diff --git a/.idea/runConfigurations/FlashcardsApplication.xml b/.idea/runConfigurations/FlashcardsApplication.xml
index 205f8c3..9b553ef 100644
--- a/.idea/runConfigurations/FlashcardsApplication.xml
+++ b/.idea/runConfigurations/FlashcardsApplication.xml
@@ -3,14 +3,14 @@
-
-
-
+
+
+
-
\ No newline at end of file
+
diff --git a/auth-server/build.gradle.kts b/auth-server/build.gradle.kts
index 178c094..16abdfb 100644
--- a/auth-server/build.gradle.kts
+++ b/auth-server/build.gradle.kts
@@ -40,10 +40,10 @@ tasks.withType {
}
val dockerHubRepo = "wisskirchenj/"
-// On ARM64 (!) uncomment the following lines to build JVM AMD64 image, since then default paketo builder is used
tasks.named("bootBuildImage") {
- builder.set("dashaun/builder:tiny")
+// buildpacks.set(listOf("paketobuildpacks/java:beta"))
+ buildpacks.set(listOf("paketobuildpacks/java-native-image:beta"))
+ builder.set("paketobuildpacks/builder-jammy-buildpackless-tiny")
imageName.set(dockerHubRepo + rootProject.name + project.name + ":" + version)
createdDate.set("now")
- environment.put("BP_NATIVE_IMAGE", "true")
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 4eb1453..bd406bd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,6 +3,7 @@ import org.springframework.boot.gradle.tasks.run.BootRun
plugins {
id("org.springframework.boot") version libs.versions.spring.boot
id("io.spring.dependency-management") version libs.versions.spring.dependency.management
+ id("org.graalvm.buildtools.native") version libs.versions.graalvm.buildtools
}
repositories {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 877a837..2b59680 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -14,28 +14,29 @@
"roboto-fontface": "*",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
- "vuetify": "^3.5.11"
+ "vuetify": "^3.5.14"
},
"devDependencies": {
"@babel/types": "^7.24.0",
- "@types/node": "^20.11.30",
+ "@types/node": "^20.12.4",
+ "@typescript-eslint/eslint-plugin": "^7.5.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitest/coverage-v8": "^1.4.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.5",
"eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.23.0",
+ "eslint-plugin-vue": "^9.24.0",
"flush-promises": "^1.0.2",
"jsdom": "^24.0.0",
"resize-observer-polyfill": "^1.5.1",
- "sass": "^1.72.0",
+ "sass": "^1.74.1",
"sonar-scanner": "^3.1.0",
- "typescript": "^5.4.3",
+ "typescript": "^5.4.4",
"unplugin-fonts": "^1.0.3",
- "vite": "^5.2.6",
+ "vite": "^5.2.8",
"vite-plugin-vuetify": "^2.0.3",
"vitest": "^1.4.0",
- "vue-tsc": "^2.0.7"
+ "vue-tsc": "^2.0.10"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -561,9 +562,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
- "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
"node_modules/@isaacs/cliui": {
@@ -928,9 +929,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.30",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
- "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
+ "version": "20.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz",
+ "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -943,16 +944,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz",
- "integrity": "sha512-zioDz623d0RHNhvx0eesUmGfIjzrk18nSBC8xewepKXbBvN/7c1qImV7Hg8TI1URTxKax7/zxfxj3Uph8Chcuw==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz",
+ "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "7.1.1",
- "@typescript-eslint/type-utils": "7.1.1",
- "@typescript-eslint/utils": "7.1.1",
- "@typescript-eslint/visitor-keys": "7.1.1",
+ "@typescript-eslint/scope-manager": "7.5.0",
+ "@typescript-eslint/type-utils": "7.5.0",
+ "@typescript-eslint/utils": "7.5.0",
+ "@typescript-eslint/visitor-keys": "7.5.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -961,7 +962,7 @@
"ts-api-utils": "^1.0.1"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -978,19 +979,19 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.1.tgz",
- "integrity": "sha512-ZWUFyL0z04R1nAEgr9e79YtV5LbafdOtN7yapNbn1ansMyaegl2D4bL7vHoJ4HPSc4CaLwuCVas8CVuneKzplQ==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz",
+ "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "7.1.1",
- "@typescript-eslint/types": "7.1.1",
- "@typescript-eslint/typescript-estree": "7.1.1",
- "@typescript-eslint/visitor-keys": "7.1.1",
+ "@typescript-eslint/scope-manager": "7.5.0",
+ "@typescript-eslint/types": "7.5.0",
+ "@typescript-eslint/typescript-estree": "7.5.0",
+ "@typescript-eslint/visitor-keys": "7.5.0",
"debug": "^4.3.4"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1006,16 +1007,16 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz",
- "integrity": "sha512-cirZpA8bJMRb4WZ+rO6+mnOJrGFDd38WoXCEI57+CYBqta8Yc8aJym2i7vyqLL1vVYljgw0X27axkUXz32T8TA==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz",
+ "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.1.1",
- "@typescript-eslint/visitor-keys": "7.1.1"
+ "@typescript-eslint/types": "7.5.0",
+ "@typescript-eslint/visitor-keys": "7.5.0"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1023,18 +1024,18 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.1.tgz",
- "integrity": "sha512-5r4RKze6XHEEhlZnJtR3GYeCh1IueUHdbrukV2KSlLXaTjuSfeVF8mZUVPLovidCuZfbVjfhi4c0DNSa/Rdg5g==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz",
+ "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "7.1.1",
- "@typescript-eslint/utils": "7.1.1",
+ "@typescript-eslint/typescript-estree": "7.5.0",
+ "@typescript-eslint/utils": "7.5.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1050,12 +1051,12 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.1.tgz",
- "integrity": "sha512-KhewzrlRMrgeKm1U9bh2z5aoL4s7K3tK5DwHDn8MHv0yQfWFz/0ZR6trrIHHa5CsF83j/GgHqzdbzCXJ3crx0Q==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz",
+ "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==",
"dev": true,
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1063,13 +1064,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.1.tgz",
- "integrity": "sha512-9ZOncVSfr+sMXVxxca2OJOPagRwT0u/UHikM2Rd6L/aB+kL/QAuTnsv6MeXtjzCJYb8PzrXarypSGIPx3Jemxw==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz",
+ "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.1.1",
- "@typescript-eslint/visitor-keys": "7.1.1",
+ "@typescript-eslint/types": "7.5.0",
+ "@typescript-eslint/visitor-keys": "7.5.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1078,7 +1079,7 @@
"ts-api-utils": "^1.0.1"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1115,21 +1116,21 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
- "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz",
+ "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "7.1.1",
- "@typescript-eslint/types": "7.1.1",
- "@typescript-eslint/typescript-estree": "7.1.1",
+ "@typescript-eslint/scope-manager": "7.5.0",
+ "@typescript-eslint/types": "7.5.0",
+ "@typescript-eslint/typescript-estree": "7.5.0",
"semver": "^7.5.4"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1140,16 +1141,16 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.1.tgz",
- "integrity": "sha512-yTdHDQxY7cSoCcAtiBzVzxleJhkGB9NncSIyMYe2+OGON1ZsP9zOPws/Pqgopa65jvknOjlk/w7ulPlZ78PiLQ==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz",
+ "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.1.1",
+ "@typescript-eslint/types": "7.5.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
@@ -1309,30 +1310,30 @@
}
},
"node_modules/@volar/language-core": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.5.tgz",
- "integrity": "sha512-u1OHmVkCFsJqNdaM2GKuMhE67TxcEnOqJNF+VtYv2Ji8DnrUaF4FAFSNxY+MRGICl+873CsSJVKas9TQtW14LA==",
+ "version": "2.2.0-alpha.5",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.5.tgz",
+ "integrity": "sha512-RqERQ8HXxKC/HAGpDg7oG/Yg8n3rC3KEnYE3D7lcKIblU59JEZX73IWD/L3fdjzyeSglDWjL91iOblU8MuKEoA==",
"dev": true,
"dependencies": {
- "@volar/source-map": "2.1.5"
+ "@volar/source-map": "2.2.0-alpha.5"
}
},
"node_modules/@volar/source-map": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.5.tgz",
- "integrity": "sha512-GIkAM6fHgDcTXcdH4i10fAiAZzO0HLIer8/pt3oZ9A0n7n4R5d1b2F8Xxzh/pgmgNoL+SrHX3MFxs35CKgfmtA==",
+ "version": "2.2.0-alpha.5",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.5.tgz",
+ "integrity": "sha512-Lw1LOPgt1QGaQX9HstRTlBz5x6d5mGq9ZTFMeyWVr8/5YOv3hCU0ehtMTwmCiAX/ZyNSINFI01ODePy2hwy06A==",
"dev": true,
"dependencies": {
"muggle-string": "^0.4.0"
}
},
"node_modules/@volar/typescript": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.5.tgz",
- "integrity": "sha512-zo9a3NrNMSkufIvHuExDGTfYv+zO7C5p2wg8fyP7vcqF/Qo0ztjb0ZfOgq/A85EO/MBc1Kj2Iu7PaOBtP++NMw==",
+ "version": "2.2.0-alpha.5",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.5.tgz",
+ "integrity": "sha512-9UKZSDTcgvKMXz9TiU1kHmu3uMuH8+M7oZ6/CzBt8LvFda+ec/ZDcvBjQg2rU5EVn4d+YPYcqenkeHre3tO7Og==",
"dev": true,
"dependencies": {
- "@volar/language-core": "2.1.5",
+ "@volar/language-core": "2.2.0-alpha.5",
"path-browserify": "^1.0.1"
}
},
@@ -1412,12 +1413,12 @@
}
},
"node_modules/@vue/language-core": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.7.tgz",
- "integrity": "sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.10.tgz",
+ "integrity": "sha512-3ULtX6hSPJNdNChi6aJ4FfdJNs5EShBLxnwLFTqrk2N1385WOwGVlbHeS2R6W9s9lXZ0+mC2bv4VlFSyeNPNGA==",
"dev": true,
"dependencies": {
- "@volar/language-core": "~2.1.3",
+ "@volar/language-core": "~2.2.0-alpha.5",
"@vue/compiler-dom": "^3.4.0",
"@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
@@ -1444,9 +1445,9 @@
}
},
"node_modules/@vue/language-core/node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+ "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -2206,12 +2207,13 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.23.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz",
- "integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==",
+ "version": "9.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
+ "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15",
@@ -2226,19 +2228,7 @@
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/eslint-scope": {
+ "node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
@@ -2254,13 +2244,16 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
- "node": ">=4.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
@@ -2292,15 +2285,6 @@
"node": ">=0.10"
}
},
- "node_modules/esquery/node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "engines": {
- "node": ">=4.0"
- }
- },
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -2313,7 +2297,7 @@
"node": ">=4.0"
}
},
- "node_modules/esrecurse/node_modules/estraverse": {
+ "node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
@@ -2469,9 +2453,9 @@
}
},
"node_modules/flatted": {
- "version": "3.2.9",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
- "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/flush-promises": {
@@ -3894,9 +3878,9 @@
"dev": true
},
"node_modules/sass": {
- "version": "1.72.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz",
- "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
+ "version": "1.74.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz",
+ "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -4297,9 +4281,9 @@
}
},
"node_modules/typescript": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
- "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz",
+ "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
@@ -4411,13 +4395,13 @@
}
},
"node_modules/vite": {
- "version": "5.2.6",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
- "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
+ "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"devOptional": true,
"dependencies": {
"esbuild": "^0.20.1",
- "postcss": "^8.4.36",
+ "postcss": "^8.4.38",
"rollup": "^4.13.0"
},
"bin": {
@@ -4621,31 +4605,6 @@
"eslint": ">=6.0.0"
}
},
- "node_modules/vue-eslint-parser/node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
- "dev": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/vue-eslint-parser/node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "engines": {
- "node": ">=4.0"
- }
- },
"node_modules/vue-router": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
@@ -4671,13 +4630,13 @@
}
},
"node_modules/vue-tsc": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.7.tgz",
- "integrity": "sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.10.tgz",
+ "integrity": "sha512-XD9GuUuc40fdL6VrfbFS5PehxK6exhKGEkzCbMjT01HcJVNuJxXaPFIhMEfxn581eryX7LBygAH6YYqnXQGElA==",
"dev": true,
"dependencies": {
- "@volar/typescript": "~2.1.3",
- "@vue/language-core": "2.0.7",
+ "@volar/typescript": "~2.2.0-alpha.5",
+ "@vue/language-core": "2.0.10",
"semver": "^7.5.4"
},
"bin": {
@@ -4688,9 +4647,9 @@
}
},
"node_modules/vuetify": {
- "version": "3.5.11",
- "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.5.11.tgz",
- "integrity": "sha512-us5I0jyFwIQYG4v41PFmVMkoc/oJddVT4C2RFjJTI99ttigbQ92gsTeG5SB8BPfmfnUS4paR5BedZwk6W3KlJw==",
+ "version": "3.5.14",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.5.14.tgz",
+ "integrity": "sha512-bmfid7K4D+wPi9h7sK4PxjmIB2tBzNuqlW14cs30iQ7GAphEeo/HYwn6aEdNK/Na+imhti8CJDDqdGs6SEfyXQ==",
"engines": {
"node": "^12.20 || >=14.13"
},
@@ -4700,10 +4659,10 @@
},
"peerDependencies": {
"typescript": ">=4.7",
- "vite-plugin-vuetify": ">=2.0.3",
+ "vite-plugin-vuetify": ">=1.0.0",
"vue": "^3.3.0",
"vue-i18n": "^9.0.0",
- "webpack-plugin-vuetify": ">=2.0.0-alpha.11"
+ "webpack-plugin-vuetify": ">=2.0.0"
},
"peerDependenciesMeta": {
"typescript": {
diff --git a/frontend/package.json b/frontend/package.json
index d208a34..0cd34e6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,27 +18,28 @@
"roboto-fontface": "*",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
- "vuetify": "^3.5.11"
+ "vuetify": "^3.5.14"
},
"devDependencies": {
"@babel/types": "^7.24.0",
- "@types/node": "^20.11.30",
+ "@types/node": "^20.12.4",
+ "@typescript-eslint/eslint-plugin": "^7.5.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitest/coverage-v8": "^1.4.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.5",
"eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.23.0",
+ "eslint-plugin-vue": "^9.24.0",
"flush-promises": "^1.0.2",
"jsdom": "^24.0.0",
"resize-observer-polyfill": "^1.5.1",
- "sass": "^1.72.0",
+ "sass": "^1.74.1",
"sonar-scanner": "^3.1.0",
- "typescript": "^5.4.3",
+ "typescript": "^5.4.4",
"unplugin-fonts": "^1.0.3",
- "vite": "^5.2.6",
+ "vite": "^5.2.8",
"vite-plugin-vuetify": "^2.0.3",
"vitest": "^1.4.0",
- "vue-tsc": "^2.0.7"
+ "vue-tsc": "^2.0.10"
}
}
diff --git a/frontend/src/feature/cards/composables/useCardsService.ts b/frontend/src/feature/cards/composables/useCardsService.ts
index d9f1ef2..2374050 100644
--- a/frontend/src/feature/cards/composables/useCardsService.ts
+++ b/frontend/src/feature/cards/composables/useCardsService.ts
@@ -7,27 +7,30 @@ const useCardsService = () => {
const getCards = async (categoryId: string, titleFilter: string, page: number) => {
return await useApi().get(ENDPOINT,
- {categoryId: categoryId, page: String(page), titleFilter: titleFilter});
+ {query: {categoryId: categoryId, page: String(page), titleFilter: titleFilter}});
};
const getCardById = async (cardId: string, categoryId: string) => {
- return await useApi().get(`${ENDPOINT}/${cardId}`, {categoryId: categoryId});
+ return await useApi().get(`${ENDPOINT}/${cardId}`, {query: {categoryId: categoryId}});
};
const getCardCount = async (categoryId: string) => {
- return await useApi().get(`${ENDPOINT}/count`, {categoryId: categoryId});
+ return await useApi().get(`${ENDPOINT}/count`, {query: {categoryId: categoryId}});
};
const putCard = async (categoryId: string, data: Card): Promise => {
- return await useApi().put(`${ENDPOINT}/${data.id}`, data, {categoryId: categoryId}, `Card ${data.question} successfully updated!`);
+ return await useApi().put(`${ENDPOINT}/${data.id}`, data,
+ {query: {categoryId: categoryId}, successMessage: `Card ${data.question} successfully updated!`});
};
const deleteCard = async (categoryId: string, data: Card) => {
- await useApi().delete(`${ENDPOINT}/${data.id}`, {categoryId: categoryId}, `Card ${data.question} successfully deleted!`);
+ await useApi().delete(`${ENDPOINT}/${data.id}`,
+ {query: {categoryId: categoryId}, successMessage: `Card ${data.question} successfully deleted!`});
};
- const createCard = async (categoryId: string, data: Card) => {
- await useApi().post(`${ENDPOINT}`, data, `Card ${data.question} successfully created!`, '',{categoryId: categoryId});
+ const postNewCard = async (categoryId: string, data: Card) => {
+ await useApi().post(`${ENDPOINT}`, data,
+ {query: {categoryId: categoryId}, successMessage: `Card ${data.question} successfully created!`});
};
return {
@@ -36,7 +39,7 @@ const useCardsService = () => {
getCardCount,
putCard,
deleteCard,
- createCard,
+ postNewCard,
};
};
export default useCardsService;
diff --git a/frontend/src/feature/cards/pages/CardsPage.vue b/frontend/src/feature/cards/pages/CardsPage.vue
index 6b6caa1..1167fcd 100644
--- a/frontend/src/feature/cards/pages/CardsPage.vue
+++ b/frontend/src/feature/cards/pages/CardsPage.vue
@@ -64,7 +64,7 @@ const addCard = async () => {
const updateCard = async (newCard: Card) => {
newCard.tags = newCard.tags.filter(tag => !!tag);
if (displayCreate.value) {
- await useCardsService().createCard(props.categoryId, newCard);
+ await useCardsService().postNewCard(props.categoryId, newCard);
card.value = {} as Card;
} else {
card.value = await useCardsService().putCard(props.categoryId, newCard);
diff --git a/frontend/src/feature/category/composables/useCategoriesService.ts b/frontend/src/feature/category/composables/useCategoriesService.ts
index 70dfc92..e387a69 100644
--- a/frontend/src/feature/category/composables/useCategoriesService.ts
+++ b/frontend/src/feature/category/composables/useCategoriesService.ts
@@ -6,7 +6,7 @@ const useCategoriesService = () => {
const ENDPOINT = '/categories';
const getCategories = async (page: number = 0) => {
- return await useApi().get(ENDPOINT, {page: String(page)});
+ return await useApi().get(ENDPOINT, {query: {page: String(page)}});
};
const getCategoryById = async (id: string) => {
@@ -14,12 +14,12 @@ const useCategoriesService = () => {
};
const postNewCategory = async (data: CategoryRequest) => {
- return await useApi().post(ENDPOINT, data, `Category ${data.name} successfully created!`);
+ return await useApi().post(ENDPOINT, data, {successMessage:`Category ${data.name} successfully created!`});
};
const putCategory = async (id: string, data: CategoryRequest): Promise => {
- return await useApi().put(`${ENDPOINT}/${id}`, data, {},
- `Category ${data.name} successfully updated!`);
+ return await useApi().put(`${ENDPOINT}/${id}`, data,
+ {successMessage: `Category ${data.name} successfully updated!`});
};
const deleteCategory = async (id: string) => {
diff --git a/frontend/src/feature/dataio/pages/DataImport.vue b/frontend/src/feature/dataio/pages/DataImport.vue
index 056249a..e83ffd0 100644
--- a/frontend/src/feature/dataio/pages/DataImport.vue
+++ b/frontend/src/feature/dataio/pages/DataImport.vue
@@ -20,7 +20,7 @@
-
@@ -36,7 +36,7 @@ import BaseCardPage from "@/shared/pages/BaseCardPage.vue";
const filePath = ref([]);
-const exportData = () => {
+const importData = () => {
console.log(filePath.value[0].text());
};
diff --git a/frontend/src/feature/registration/composables/useRegistrationService.ts b/frontend/src/feature/registration/composables/useRegistrationService.ts
index cd11a25..3991712 100644
--- a/frontend/src/feature/registration/composables/useRegistrationService.ts
+++ b/frontend/src/feature/registration/composables/useRegistrationService.ts
@@ -7,8 +7,8 @@ const useRegistrationService = () => {
email: email,
password: password,
};
- await useApi().post("/register", data,`User ${email} registered successfully!`,
- 'Failed to register user');
+ await useApi().post("/register", data,
+ {successMessage: `User ${email} registered successfully!`, errorMessage: 'Failed to register user'});
};
return {
diff --git a/frontend/src/shared/composables/errorService.ts b/frontend/src/shared/composables/errorService.ts
index 0754cdb..5f97c7c 100644
--- a/frontend/src/shared/composables/errorService.ts
+++ b/frontend/src/shared/composables/errorService.ts
@@ -11,7 +11,7 @@ export const useErrorService = () => {
const errorState = {} as ErrorState;
errorState.code = (error.isAxiosError && error.response)
? `Error status ${error.response.status}`
- : 'Unknown Error';
+ : error.message ?? 'Unknown Error';
errorState.message = (error.isAxiosError && error.response?.data)
? error.response.data.message
: message ?? 'Service Unavailable';
diff --git a/frontend/src/shared/composables/useApi.ts b/frontend/src/shared/composables/useApi.ts
index 37604bd..14b5af1 100644
--- a/frontend/src/shared/composables/useApi.ts
+++ b/frontend/src/shared/composables/useApi.ts
@@ -1,87 +1,85 @@
import apiClient from '@/plugins/axios';
-import {ErrorState, useErrorService} from "@/shared/composables/errorService";
+import {useErrorService} from "@/shared/composables/errorService";
import {useToastService} from "@/shared/composables/toastService";
+
const useApi = () => {
- const handleNonAxiosError = (error: any, customError?: ErrorState) => {
- customError
- ? useErrorService().handleAndNotify(customError.code, customError.message)
- : useErrorService().handleAndThrow(error);
+ interface ApiOptions {
+ successMessage?: string;
+ errorMessage?: string;
+ query?: Record;
+ }
+
+ const joinUrlAndQueryParams = (url: string, query?: Record) => {
+ return query
+ ? `${url}?${Object.keys(query).map(key => key + '=' + query[key]).join('&')}`
+ : url;
};
return {
- post: async (url: string, requestData: R, successMessage?: string,
- errorMessage?: string, query?: Record, customError?: ErrorState) => {
+ post: async (url: string, requestData: R, options: ApiOptions = {}) => {
+ const urlWithParams = joinUrlAndQueryParams(url, options.query);
+ const errorMessage = options.errorMessage ?? 'Failed to post';
try {
- const urlWithParams = query ?
- `${url}?${Object.keys(query).map(key => key + '=' + query[key]).join('&')}` : url;
const response = await apiClient.post(urlWithParams, requestData);
if (response.status !== 200 && response.status !== 201) {
- useErrorService().handleAndNotify(
- `Error status code ${response.status}!`, errorMessage ?? 'Failed to post');
+ useErrorService().handleAndNotify(`Error status code ${response.status}!`, errorMessage);
} else {
- useToastService().showSuccess(successMessage ?? `Successfully posted to ${url}!`);
+ useToastService().showSuccess(options.successMessage ?? `Successfully posted to ${url}!`);
}
} catch (error: any) {
- handleNonAxiosError(error, customError);
+ useErrorService().handleAndThrow(error, errorMessage);
}
},
- get: async (url: string, query?: Record,
- errorMessage?: string, customError?: ErrorState): Promise => {
- const urlWithParams = query ?
- `${url}?${Object.keys(query).map(key => key + '=' + query[key]).join('&')}` : url;
-
+ get: async (url: string, options: ApiOptions = {}): Promise => {
+ const urlWithParams = joinUrlAndQueryParams(url, options.query);
+ const errorMessage = options.errorMessage ?? 'Failed to load ${urlWithParams}';
try {
const response = await apiClient.get(urlWithParams);
if (response.status !== 200) {
- useErrorService().handleAndNotify(
- `Error status code ${response.status}!`, errorMessage ?? `Failed to load ${urlWithParams}`);
+ useErrorService().handleAndNotify(`Error status code ${response.status}!`, errorMessage);
return {} as R;
} else {
return response.data;
}
} catch (error: any) {
- handleNonAxiosError(error, customError);
+ useErrorService().handleAndThrow(error, errorMessage);
return {} as R;
}
},
- put: async (url: string, requestData: R, query?: Record, successMessage?: string,
- errorMessage?: string, customError?: ErrorState): Promise => {
- const urlWithParams = query ?
- `${url}?${Object.keys(query).map(key => key + '=' + query[key]).join('&')}` : url;
+ put: async (url: string, requestData: R, options: ApiOptions = {}): Promise => {
+ const urlWithParams = joinUrlAndQueryParams(url, options.query);
+ const errorMessage = options.errorMessage ?? 'Failed to update';
try {
const response = await apiClient.put(urlWithParams, requestData);
if (response.status != 200) {
- useErrorService().handleAndNotify(
- `Error status code ${response.status}!`, errorMessage ?? 'Failed to update');
+ useErrorService().handleAndNotify(`Error status code ${response.status}!`, errorMessage);
return {} as S;
} else {
- useToastService().showSuccess(successMessage ?? `Successfully updated ${url}!`);
+ useToastService().showSuccess(options.successMessage ?? `Successfully updated ${url}!`);
return response.data;
}
} catch (error: any) {
- handleNonAxiosError(error, customError);
+ useErrorService().handleAndThrow(error, errorMessage);
return {} as S;
}
},
- delete: async (url: string, query?: Record, successMessage?: string,
- errorMessage?: string, customError?: ErrorState) => {
- const urlWithParams = query ?
- `${url}?${Object.keys(query).map(key => key + '=' + query[key]).join('&')}` : url;
+ delete: async (url: string, options: ApiOptions = {}) => {
+ const urlWithParams = joinUrlAndQueryParams(url, options.query);
+ const errorMessage = options.errorMessage ?? 'Failed to delete';
try {
const response = await apiClient.delete(urlWithParams);
if (response.status != 200) {
- useErrorService().handleAndNotify(
- `Error status code ${response.status}!`, errorMessage ?? 'Failed to delete');
+ useErrorService().handleAndNotify(`Error status code ${response.status}!`, errorMessage);
} else {
- useToastService().showSuccess(successMessage ?? `Successfully deleted ${url}!`);
+ useToastService().showSuccess(options.successMessage ?? `Successfully deleted ${url}!`);
}
} catch (error: any) {
- handleNonAxiosError(error, customError);
+ useErrorService().handleAndThrow(error, errorMessage);
}
}
};
diff --git a/frontend/test/feature/cards/useCardsService.test.ts b/frontend/test/feature/cards/useCardsService.test.ts
new file mode 100644
index 0000000..19db2ba
--- /dev/null
+++ b/frontend/test/feature/cards/useCardsService.test.ts
@@ -0,0 +1,86 @@
+import useApi from "@/shared/composables/useApi";
+import useCardsService from "@/feature/cards/composables/useCardsService.ts";
+import {Card, CardItem, CardPage, CardType} from "@/feature/cards/model/card.ts";
+
+vi.mock('@/shared/composables/useApi', () => {
+ return {
+ default: vi.fn().mockReturnValue({
+ get: vi.fn(),
+ post: vi.fn(),
+ put: vi.fn(),
+ delete: vi.fn(),
+ })
+ };
+});
+
+describe('useCardsService', () => {
+
+ const testCard: CardItem = {
+ id: '1',
+ question: 'Test question',
+ type: CardType.SIMPLEQA,
+ };
+
+ const otherCard: CardItem = {
+ id: '2',
+ question: 'Other question',
+ type: CardType.MULTIPLE_CHOICE,
+ };
+
+ const page: CardPage = {
+ cards: [testCard, otherCard],
+ currentPage: 1,
+ isLast: true
+ };
+
+ const card : Card = {
+ id: '1',
+ type: CardType.SIMPLEQA,
+ tags: [],
+ question: 'question'
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('getCards should return cards in page', async () => {
+ vi.mocked(useApi().get).mockResolvedValue(page);
+ await expect(useCardsService().getCards('catid','filter', 0)).resolves.toEqual(page);
+ expect(useApi().get).toHaveBeenCalledWith('/cards',
+ {query: {categoryId: 'catid', page: '0', titleFilter: 'filter'}});
+ });
+
+ it('getCardById should return category by id', async () => {
+ vi.mocked(useApi().get).mockResolvedValue(testCard);
+ await expect(useCardsService().getCardById('1', 'catid')).resolves.toEqual(testCard);
+ expect(useApi().get).toHaveBeenCalledWith('/cards/1', {query: {categoryId: 'catid',}});
+ });
+
+
+ it('getCardCount should return # of cards', async () => {
+ vi.mocked(useApi().get).mockResolvedValue(15);
+ await expect(useCardsService().getCardCount('catid')).resolves.toEqual(15);
+ expect(useApi().get).toHaveBeenCalledWith('/cards/count', {query: {categoryId: 'catid',}});
+ });
+
+ it('postNewCard should return card created', async () => {
+ await useCardsService().postNewCard('catid', card);
+ expect(useApi().post).toHaveBeenCalledWith('/cards', card,
+ {successMessage: 'Card question successfully created!', query: {categoryId: 'catid'}});
+ });
+
+
+ it('putCard should return card updated', async () => {
+ await useCardsService().putCard('catid', card);
+ expect(useApi().put).toHaveBeenCalledWith('/cards/1', card,
+ {successMessage: 'Card question successfully updated!', query: {categoryId: 'catid'}});
+ });
+
+ it('deleteCard should return card deleted', async () => {
+ await useCardsService().deleteCard('catid', card);
+ expect(useApi().delete).toHaveBeenCalledWith('/cards/1',
+ {successMessage: 'Card question successfully deleted!', query: {categoryId: 'catid'}});
+ });
+
+});
diff --git a/frontend/test/feature/category/useCategoriesService.test.ts b/frontend/test/feature/category/useCategoriesService.test.ts
index f0ec45a..f18d39d 100644
--- a/frontend/test/feature/category/useCategoriesService.test.ts
+++ b/frontend/test/feature/category/useCategoriesService.test.ts
@@ -43,7 +43,7 @@ describe('useCategoriesService', () => {
it('getCategories should return categories in page', async () => {
vi.mocked(useApi().get).mockResolvedValue(page);
await expect(useCategoriesService().getCategories()).resolves.toEqual(page);
- expect(useApi().get).toHaveBeenCalledWith('/categories', {page: '0'});
+ expect(useApi().get).toHaveBeenCalledWith('/categories', {query: {page: '0'}});
});
it('getCategoryById should return category by id', async () => {
@@ -55,14 +55,16 @@ describe('useCategoriesService', () => {
it('postNewCategory should return category created', async () => {
const newCat = {name: 'New Category', description: 'New Description'};
await useCategoriesService().postNewCategory(newCat);
- expect(useApi().post).toHaveBeenCalledWith('/categories', newCat, 'Category New Category successfully created!');
+ expect(useApi().post).toHaveBeenCalledWith('/categories', newCat,
+ {successMessage: 'Category New Category successfully created!'});
});
it('putCategory should return category updated', async () => {
const updatedCat = {name: 'Updated Category', description: 'Updated Description'};
await useCategoriesService().putCategory('1', updatedCat);
- expect(useApi().put).toHaveBeenCalledWith('/categories/1', updatedCat, {}, 'Category Updated Category successfully updated!');
+ expect(useApi().put).toHaveBeenCalledWith('/categories/1', updatedCat,
+ {successMessage: 'Category Updated Category successfully updated!'});
});
it('deleteCategory should return category removed', async () => {
diff --git a/frontend/test/feature/registration/useRegistrationService.test.ts b/frontend/test/feature/registration/useRegistrationService.test.ts
index ecc291e..1f9eda8 100644
--- a/frontend/test/feature/registration/useRegistrationService.test.ts
+++ b/frontend/test/feature/registration/useRegistrationService.test.ts
@@ -18,6 +18,7 @@ describe('useRegistrationService', () => {
const email = 'email@test.com';
const password = 'password';
await useRegistrationService().postNewUser(email, password);
- expect(useApi().post).toHaveBeenCalledWith('/register', {email, password}, `User ${email} registered successfully!`, 'Failed to register user');
+ expect(useApi().post).toHaveBeenCalledWith('/register', {email, password},
+ {successMessage: `User ${email} registered successfully!`, errorMessage: 'Failed to register user'});
});
});
diff --git a/frontend/test/shared/errorService.test.ts b/frontend/test/shared/errorService.test.ts
index c5d4b33..024d03d 100644
--- a/frontend/test/shared/errorService.test.ts
+++ b/frontend/test/shared/errorService.test.ts
@@ -17,7 +17,8 @@ describe('errorService', () => {
});
it('should handle and throw errors correctly', () => {
- const error = new Error('Test Error');
+ const testErrorMessage = 'Test Error';
+ const error = new Error(testErrorMessage);
const axiosError = {
isAxiosError: true,
response: {
@@ -28,9 +29,9 @@ describe('errorService', () => {
}
};
expect(() => useErrorService().handleAndThrow(axiosError)).toThrowError('Not Found');
- expect(useToastService().showError).toHaveBeenCalledWith('Not Found','Error status 404');
- expect(() => useErrorService().handleAndThrow(error)).toThrowError('Unknown Error');
- expect(useToastService().showError).toHaveBeenCalledWith('Service Unavailable','Unknown Error');
+ expect(useToastService().showError).toHaveBeenCalledWith('Not Found', 'Error status 404');
+ expect(() => useErrorService().handleAndThrow(error)).toThrowError(testErrorMessage);
+ expect(useToastService().showError).toHaveBeenCalledWith('Service Unavailable', testErrorMessage);
});
it('should handle and notify errors correctly', () => {
diff --git a/frontend/test/shared/useApi.test.ts b/frontend/test/shared/useApi.test.ts
index 27c89f5..b7e48d5 100644
--- a/frontend/test/shared/useApi.test.ts
+++ b/frontend/test/shared/useApi.test.ts
@@ -20,20 +20,17 @@ describe('useApi', () => {
const testUrl = '/testUrl';
- it('should show Service unavailable and throw if Axios throws', async () => {
- vi.mocked(apiClient.delete).mockRejectedValue(new Error('Test Error'));
- await expect(useApi().delete(testUrl)).rejects.toThrowError('Unknown Error: Service Unavailable');
-
- expect(apiClient.delete).toHaveBeenCalledWith(testUrl);
- expect(useToastService().showError).toHaveBeenCalledWith('Service Unavailable', 'Unknown Error');
- });
-
- it('should show Custom Error no throw if Axios throws', async () => {
- vi.mocked(apiClient.delete).mockRejectedValue(new Error('Test Error'));
-
- await useApi().delete(testUrl, undefined,'', '', {code: 'Error 999', message: 'No Service'});
+ it('should show thrown error if Axios throws', async () => {
+ const testErrorMessage = 'Test Error';
+ vi.mocked(apiClient.delete).mockRejectedValue(new Error(testErrorMessage));
+
+ try {
+ await useApi().delete(testUrl);
+ } catch (e) {
+ expect(e).toBeInstanceOf(Error);
+ }
expect(apiClient.delete).toHaveBeenCalledWith(testUrl);
- expect(useToastService().showError).toHaveBeenCalledWith('No Service', 'Error 999');
+ expect(useToastService().showError).toHaveBeenCalledWith('Failed to delete', testErrorMessage);
});
it.each(
@@ -42,7 +39,7 @@ describe('useApi', () => {
vi.mocked(apiClient.delete).mockResolvedValue({status: 200});
const successMessage = customSuccess?? `Successfully deleted ${testUrl}!`;
- await useApi().delete(testUrl, undefined, customSuccess);
+ await useApi().delete(testUrl, {successMessage: successMessage});
expect(apiClient.delete).toHaveBeenCalledWith(testUrl);
expect(useToastService().showSuccess).toHaveBeenCalledWith(successMessage);
});
@@ -52,7 +49,7 @@ describe('useApi', () => {
vi.mocked(apiClient.delete).mockResolvedValue({status: 403});
const errorMessage = customError?? 'Failed to delete';
- await useApi().delete(testUrl, undefined,'', customError);
+ await useApi().delete(testUrl, {errorMessage: errorMessage});
expect(apiClient.delete).toHaveBeenCalledWith(testUrl);
expect(useToastService().showError).toHaveBeenCalledWith(errorMessage, 'Error status code 403!');
});
@@ -80,7 +77,7 @@ describe('useApi', () => {
const responseData = {someData: 'someData'};
vi.mocked(apiClient.get).mockResolvedValue({status: 200, data: responseData});
- await expect(useApi().get(testUrl, {id: '123', filter: 'xxx'})).resolves.toEqual(responseData);
+ await expect(useApi().get(testUrl, {query: {id: '123', filter: 'xxx'}})).resolves.toEqual(responseData);
const urlWithParams = `${testUrl}?id=123&filter=xxx`;
expect(apiClient.get).toHaveBeenCalledWith(urlWithParams);
});
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e71c09e..3e9ae04 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,6 +2,6 @@
spring-boot = "3.2.4"
spring-dependency-management = "1.1.4"
node-gradle = "7.0.2"
-sonar-gradle = "4.4.1.3373"
-node-js = "21.7.1"
+sonar-gradle = "5.0.0.4638"
+node-js = "21.7.2"
graalvm-buildtools = "0.10.1"
diff --git a/server/src/main/java/org/hyperskill/community/flashcards/FlashcardsApplication.java b/server/src/main/java/org/hyperskill/community/flashcards/FlashcardsApplication.java
index 8762da4..c9f13dc 100644
--- a/server/src/main/java/org/hyperskill/community/flashcards/FlashcardsApplication.java
+++ b/server/src/main/java/org/hyperskill/community/flashcards/FlashcardsApplication.java
@@ -19,15 +19,15 @@ public static void main(String[] args) {
SpringApplication.run(FlashcardsApplication.class, args);
}
- @Bean
- @Profile("!test")
- public CommandLineRunner logBuildInfo(BuildProperties buildProperties, GitProperties gitProperties) {
- return args -> {
- log.info("Build-Info: Group: {} Artifact: {} Version: {}, Buildtime: {}", buildProperties.getGroup(), buildProperties.getArtifact(),
- buildProperties.getVersion(), buildProperties.getTime().atZone(ZoneId.systemDefault()));
- log.info("Git-Info: Branch: {} Commit: {}, From: {}", gitProperties.getBranch(), gitProperties.getShortCommitId(),
- gitProperties.getCommitTime().atZone(ZoneId.systemDefault()));
- };
- }
+ @Bean
+ @Profile("!test")
+ public CommandLineRunner logBuildInfo(BuildProperties buildProperties, GitProperties gitProperties) {
+ return args -> {
+ log.info("Build-Info: Group: {} Artifact: {} Version: {}, Buildtime: {}", buildProperties.getGroup(), buildProperties.getArtifact(),
+ buildProperties.getVersion(), buildProperties.getTime().atZone(ZoneId.systemDefault()));
+ log.info("Git-Info: Branch: {} Commit: {}, From: {}", gitProperties.getBranch(), gitProperties.getShortCommitId(),
+ gitProperties.getCommitTime().atZone(ZoneId.systemDefault()));
+ };
+ }
}
diff --git a/server/src/main/java/org/hyperskill/community/flashcards/common/ActionsParser.java b/server/src/main/java/org/hyperskill/community/flashcards/common/ActionsParser.java
index 3be202a..8a8e2e2 100644
--- a/server/src/main/java/org/hyperskill/community/flashcards/common/ActionsParser.java
+++ b/server/src/main/java/org/hyperskill/community/flashcards/common/ActionsParser.java
@@ -9,19 +9,13 @@
public class ActionsParser {
private ActionsParser() {
+ // no instantiation
}
public static Set fromPermissions(String permissions, String uri) {
Set actions = HashSet.newHashSet(3);
- if (permissions.contains("r")) {
- actions.add(new PermittedAction(ActionType.READ, uri));
- }
- if (permissions.contains("w")) {
- actions.add(new PermittedAction(ActionType.WRITE, uri));
- }
- if (permissions.contains("d")) {
- actions.add(new PermittedAction(ActionType.DELETE, uri));
- }
+ permissions.chars()
+ .forEach(c -> actions.add(new PermittedAction(ActionType.fromChar((char) c), uri)));
return actions;
}
}
diff --git a/server/src/main/java/org/hyperskill/community/flashcards/common/response/ActionType.java b/server/src/main/java/org/hyperskill/community/flashcards/common/response/ActionType.java
index 1c7b68e..03d6edf 100644
--- a/server/src/main/java/org/hyperskill/community/flashcards/common/response/ActionType.java
+++ b/server/src/main/java/org/hyperskill/community/flashcards/common/response/ActionType.java
@@ -1,5 +1,16 @@
package org.hyperskill.community.flashcards.common.response;
public enum ActionType {
- READ, WRITE, DELETE
+ READ,
+ WRITE,
+ DELETE;
+
+ public static ActionType fromChar(char c) {
+ return switch (c) {
+ case 'r' -> READ;
+ case 'w' -> WRITE;
+ case 'd' -> DELETE;
+ default -> throw new IllegalArgumentException("Unknown action type: " + c);
+ };
+ }
}