From 3db81fa45a27d91fd3662bb9feb467c82aeeb9a5 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 17 Oct 2023 19:49:24 +0300 Subject: [PATCH 1/3] Redirect to `/vuln` (#2738) --- .../save/frontend/components/views/index/IndexView.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/IndexView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/IndexView.kt index 33bead61ba..15385e71b4 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/IndexView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/IndexView.kt @@ -14,10 +14,19 @@ import react.Props import react.dom.html.ReactHTML.div import react.dom.html.ReactHTML.main +import react.router.useNavigate +import react.useEffect import web.cssom.* +import kotlinx.browser.window @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") val indexView: FC = FC { props -> + val navigate = useNavigate() + useEffect { + if (window.location.run { hostname in setOf("cosv.dev", "cosv.gitlink.org.cn") && pathname == "/" }) { + navigate("/vuln") + } + } useBackground(Style.INDEX) particles() main { From b6ae0c9a9d10d3cb3378fd034ac500898faad477 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Wed, 18 Oct 2023 08:57:31 +0300 Subject: [PATCH 2/3] Adding RAW data to the VulnerabilityView (#2741) --- .../save/validation/FrontendRoutes.kt | 2 +- save-frontend/build.gradle.kts | 1 + .../components/basic/AvatarRenderers.kt | 4 +- .../save/frontend/components/basic/Forum.kt | 4 +- .../frontend/components/basic/UserBoard.kt | 2 +- .../components/topbar/TopBarUserField.kt | 2 +- .../components/views/index/CardUser.kt | 4 +- .../views/userprofile/UserProfileView.kt | 2 +- .../usersettings/SettingsViewLeftColumn.kt | 2 +- .../views/vuln/VulnerabilityChangesTab.kt | 4 +- .../views/vuln/VulnerabilityHeader.kt | 21 ++- .../views/vuln/VulnerabilityRawDataTab.kt | 29 ++++ .../views/vuln/VulnerabilityView.kt | 7 +- .../frontend/externals/jsonview/JsonView.kt | 38 +++++ .../save/frontend/routing/BasicRouting.kt | 2 +- yarn.lock | 140 +++++++++++++++++- 16 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityRawDataTab.kt create mode 100644 save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/jsonview/JsonView.kt diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/FrontendRoutes.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/FrontendRoutes.kt index 5daacc4c4b..80c2c2ca23 100644 --- a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/FrontendRoutes.kt +++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/FrontendRoutes.kt @@ -31,7 +31,6 @@ enum class FrontendRoutes(val path: String) { INDEX(""), MANAGE_ORGANIZATIONS("organizations"), NOT_FOUND("not-found"), - PROFILE("profile"), PROJECTS("projects"), REGISTRATION("registration"), SANDBOX("sandbox"), @@ -48,6 +47,7 @@ enum class FrontendRoutes(val path: String) { VULNERABILITIES("$VULN/list"), VULNERABILITY_SINGLE("$VULN/collection"), VULN_COSV_SCHEMA("$VULN/schema"), + VULN_PROFILE("$VULN/profile"), VULN_TOP_RATING("$VULN/top-rating"), ; diff --git a/save-frontend/build.gradle.kts b/save-frontend/build.gradle.kts index e4db4c4718..82fe9df8d8 100644 --- a/save-frontend/build.gradle.kts +++ b/save-frontend/build.gradle.kts @@ -113,6 +113,7 @@ kotlin { implementation(npm("react-avatar-image-cropper", "^1.4.2")) implementation(npm("react-circle", "^1.1.1")) implementation(npm("react-diff-viewer-continued", "^3.2.6")) + implementation(npm("react-json-view", "^1.21.3")) implementation(npm("multi-range-slider-react", "^2.0.5")) // react-sigma implementation(npm("@react-sigma/core", "^3.1.0")) diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/AvatarRenderers.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/AvatarRenderers.kt index d3a98ae0c5..a35b99d669 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/AvatarRenderers.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/AvatarRenderers.kt @@ -95,7 +95,7 @@ fun ChildrenBuilder.renderAvatar( isLinkActive: Boolean = true, styleBuilder: CSSProperties.() -> Unit, ) { - val newLink = (link ?: "/${FrontendRoutes.PROFILE}/${userInfo?.name}").takeIf { userInfo?.status != UserStatus.DELETED && isLinkActive } + val newLink = (link ?: "/${FrontendRoutes.VULN_PROFILE}/${userInfo?.name}").takeIf { userInfo?.status != UserStatus.DELETED && isLinkActive } return renderAvatar( userInfo?.avatar?.avatarRenderer() ?: AVATAR_PROFILE_PLACEHOLDER, classes, @@ -151,7 +151,7 @@ fun ChildrenBuilder.renderUserAvatarWithName( } return if (userInfo.status != UserStatus.DELETED) { Link { - to = "/${FrontendRoutes.PROFILE}/${userInfo.name}" + to = "/${FrontendRoutes.VULN_PROFILE}/${userInfo.name}" renderImg() } } else { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/Forum.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/Forum.kt index 3f3dea26c6..79d288a940 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/Forum.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/Forum.kt @@ -210,7 +210,7 @@ private fun ChildrenBuilder.renderLeftColumn( width = 80.0 onError = { setAvatar(AVATAR_PLACEHOLDER) } } - to = "/${FrontendRoutes.PROFILE}/$name" + to = "/${FrontendRoutes.VULN_PROFILE}/$name" } } div { @@ -230,7 +230,7 @@ private fun ChildrenBuilder.renderLeftColumn( h1 { className = ClassName("col-12 text-center font-weight-bold h5") Link { - to = "/${FrontendRoutes.PROFILE}/$name" + to = "/${FrontendRoutes.VULN_PROFILE}/$name" +name } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/UserBoard.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/UserBoard.kt index ed20e3f65b..0131b8e598 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/UserBoard.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/UserBoard.kt @@ -30,7 +30,7 @@ val userBoard: FC = FC { props -> div { className = ClassName(props.avatarOuterClasses.orEmpty()) figure { - renderAvatar(user, props.avatarInnerClasses.orEmpty(), "/${FrontendRoutes.PROFILE}/${user.name}") { + renderAvatar(user, props.avatarInnerClasses.orEmpty(), "/${FrontendRoutes.VULN_PROFILE}/${user.name}") { // just some default values in case you don't want to provide value // in this case you will get small avatar width = props.widthAndHeight ?: 4.rem diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/topbar/TopBarUserField.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/topbar/TopBarUserField.kt index b6fa9b5bd9..dbdab78798 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/topbar/TopBarUserField.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/topbar/TopBarUserField.kt @@ -101,7 +101,7 @@ val topBarUserField: FC = FC { props -> props.userInfo?.name?.let { name -> dropdownEntry(faUser, "Profile".t()) { attrs -> attrs.onClick = { - navigate(to = "/${FrontendRoutes.PROFILE}/$name") + navigate(to = "/${FrontendRoutes.VULN_PROFILE}/$name") } } dropdownEntry(faCog, "Settings".t()) { attrs -> diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt index 9e68945562..3cf412bbf3 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt @@ -82,7 +82,7 @@ val cardUser: FC = FC { props -> } +"${"Welcome".t()}${props.userInfo?.name?.let { ", " } ?: ""}" Link { - to = "/${FrontendRoutes.PROFILE}/${props.userInfo?.name}" + to = "/${FrontendRoutes.VULN_PROFILE}/${props.userInfo?.name}" b { +(props.userInfo?.name?.let { " @$it " } ?: "") } @@ -242,7 +242,7 @@ val cardUser: FC = FC { props -> className = ClassName("col-3") p { Link { - to = "/${FrontendRoutes.PROFILE}/${props.userInfo?.name}" + to = "/${FrontendRoutes.VULN_PROFILE}/${props.userInfo?.name}" +countVulnerability.toString() } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt index 4320eefd98..b014fee83f 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt @@ -137,7 +137,7 @@ enum class UserProfileTab { companion object : TabMenuBar { override val nameOfTheHeadUrlSection = "" override val defaultTab: UserProfileTab = VULNERABILITIES - override val regexForUrlClassification = "/${FrontendRoutes.PROFILE}" + override val regexForUrlClassification = "/${FrontendRoutes.VULN_PROFILE}" override fun valueOf(elem: String): UserProfileTab = UserProfileTab.valueOf(elem) override fun values(): Array = UserProfileTab.values() } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/SettingsViewLeftColumn.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/SettingsViewLeftColumn.kt index 1c2e00fcfe..1856fb1cd1 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/SettingsViewLeftColumn.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/SettingsViewLeftColumn.kt @@ -80,7 +80,7 @@ val leftSettingsColumn: FC = FC { props -> className = ClassName("row justify-content-center") h6 { Link { - to = "/${FrontendRoutes.PROFILE}/${props.userInfo?.name}" + to = "/${FrontendRoutes.VULN_PROFILE}/${props.userInfo?.name}" style = jso { textDecoration = TextDecoration.underline } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityChangesTab.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityChangesTab.kt index 4899f912e2..225625975a 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityChangesTab.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityChangesTab.kt @@ -20,7 +20,7 @@ import kotlinx.serialization.json.Json private val json = Json { prettyPrint = true } -val vulnerabilityChangesTab: FC = FC { props -> +val vulnerabilityChangesTab: FC = FC { props -> val (cosvVersions, setCosvVersions) = useState(emptyList()) @@ -126,7 +126,7 @@ val vulnerabilityChangesTab: FC = FC { props -> /** * [Props] of vulnerability changes tab component */ -external interface VulnerabilityChangesTabProps : Props { +external interface VulnerabilityChangesTab : Props { /** * Vulnerability identifier */ diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityHeader.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityHeader.kt index b017ff41d3..673cf3aea0 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityHeader.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityHeader.kt @@ -244,17 +244,16 @@ internal val headerMenu: FC = FC { props -> props.setIsTableView { !it } } } - if (props.selectedMenu == VulnerabilityTab.INFO) { - a { - buttonBuilder( - faDownload, - "secondary", - classes = "mr-2", - isOutline = true, - title = "Download vulnerability in COSV".t(), - ) { } - href = "$apiUrl/vulnerabilities/download?identifier=${props.vulnerability.cosv.id}" - } + + a { + buttonBuilder( + faDownload, + "secondary", + classes = "mr-2", + isOutline = true, + title = "Download vulnerability in COSV".t(), + ) { } + href = "$apiUrl/vulnerabilities/download?identifier=${props.vulnerability.cosv.id}" } if (props.permissions.isSuperAdmin || diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityRawDataTab.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityRawDataTab.kt new file mode 100644 index 0000000000..375a7fd1c0 --- /dev/null +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityRawDataTab.kt @@ -0,0 +1,29 @@ +@file:Suppress("FILE_NAME_MATCH_CLASS", "HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE") + +package com.saveourtool.save.frontend.components.views.vuln + +import com.saveourtool.save.entities.cosv.VulnerabilityExt +import com.saveourtool.save.frontend.externals.jsonview.reactJson +import react.FC +import react.Props + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +val vulnerabilityRawDataTab: FC = FC { props -> + reactJson { + src = JSON.parse(Json.encodeToString(props.vulnerability.cosv)) + name = false + theme = "bright:inverted" + } +} + +/** + * [Props] of vulnerability changes tab component + */ +external interface VulnerabilityRawDataTabProps : Props { + /** + * Vulnerability identifier + */ + var vulnerability: VulnerabilityExt +} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt index b26d435de0..4402fb5dd2 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt @@ -147,6 +147,10 @@ val vulnerabilityView: FC = FC { props -> VulnerabilityTab.CHANGES -> vulnerabilityChangesTab { this.identifier = props.identifier } + + VulnerabilityTab.RAW -> vulnerabilityRawDataTab { + this.vulnerability = vulnerability + } } } } @@ -163,13 +167,14 @@ enum class VulnerabilityTab { COMMENTS, HISTORY, CHANGES, + RAW, // PROPOSED_CHANGES, ; companion object : TabMenuBar { override val nameOfTheHeadUrlSection = "" override val defaultTab: VulnerabilityTab = INFO - override val regexForUrlClassification = "/${FrontendRoutes.PROFILE}" + override val regexForUrlClassification = "/${FrontendRoutes.VULN_PROFILE}" override fun valueOf(elem: String): VulnerabilityTab = VulnerabilityTab.valueOf(elem) override fun values(): Array = VulnerabilityTab.values() } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/jsonview/JsonView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/jsonview/JsonView.kt new file mode 100644 index 0000000000..d851f5f24a --- /dev/null +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/jsonview/JsonView.kt @@ -0,0 +1,38 @@ +@file:Suppress("HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", "FILE_NAME_MATCH_CLASS") +@file:JsModule("react-json-view") +@file:JsNonModule + +package com.saveourtool.save.frontend.externals.jsonview + +import react.FC +import react.Props +import kotlin.js.Json + +/** + * External declaration of [reactJson] react component + */ +@JsName("default") +external val reactJson: FC + +/** + * Props of [ReactJsonViewProps] + */ +external interface ReactJsonViewProps : Props { + /** + * Input JSON + */ + var src: Json + + /** + * name : {} or no name in case of false + */ + var name: Boolean + + /** + * theme for the background + */ + var theme: String + // FixMe: onAdd?: ((add: InteractionProps) => false | any) | false; + // FixMe: onEdit?: ((edit: InteractionProps) => false | any) | false; + // FixMe: onDelete?: ((del: InteractionProps) => false | any) | false; +} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt index 7329c5afcb..cb6ebef215 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt @@ -206,7 +206,7 @@ val basicRouting: FC = FC { props -> uploadVulnerabilityView.create() to UPLOAD_VULNERABILITY, vulnerabilityView.create() to "$VULNERABILITY_SINGLE/:identifier", demoCollectionView.create() to DEMO, - userProfileView.create() to "$PROFILE/:name", + userProfileView.create() to "$VULN_PROFILE/:name", topRatingView.create() to VULN_TOP_RATING, termsOfUsageView.create() to TERMS_OF_USE, cookieTermsOfUse.create() to COOKIE, diff --git a/yarn.lock b/yarn.lock index a27ede8d9e..b504d206fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -66,10 +66,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.23.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d" - integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g== +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: regenerator-runtime "^0.14.0" @@ -1310,6 +1310,11 @@ as-number@^1.0.0: resolved "https://registry.yarnpkg.com/as-number/-/as-number-1.0.0.tgz#acb27e34f8f9d8ab0da9e376f3b8959860f80a66" integrity sha512-HkI/zLo2AbSRO4fqVkmyf3hms0bJDs3iboHqTrNuwTiCRvdYXM7HFhfhB6Dk51anV2LM/IMB83mtK9mHw4FlAg== +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -1437,6 +1442,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -1955,6 +1965,13 @@ cross-fetch@3.1.6: dependencies: node-fetch "^2.6.11" +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2777,6 +2794,31 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" +fbemitter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" + integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== + dependencies: + fbjs "^3.0.0" + +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.0, fbjs@^3.0.1: + version "3.0.5" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" + integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^1.0.35" + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -2863,6 +2905,14 @@ flatten-vertex-data@^1.0.0: dependencies: dtype "^2.0.0" +flux@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572" + integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw== + dependencies: + fbemitter "^3.0.0" + fbjs "^3.0.1" + follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.15.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" @@ -3994,11 +4044,21 @@ lodash-es@4: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -4661,7 +4721,7 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.11, node-fetch@^2.6.7: +node-fetch@^2.6.11, node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -5122,6 +5182,13 @@ promise-polyfill@^3.1.0: resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-3.1.0.tgz#62952b01d059b115b432763b7ef461b80f6df47d" integrity sha512-t20OwHJ4ZOUj5fV+qms67oczphAVkRC6Rrjcrne+V1FJkQMym7n69xJmYyXHulm9OUQ0Ie5KSzg0QhOYgaxy+w== +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prop-types@15, prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -5154,6 +5221,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== + qjobs@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" @@ -5222,6 +5294,16 @@ react-avatar-image-cropper@^1.4.2: resolved "https://registry.yarnpkg.com/react-avatar-image-cropper/-/react-avatar-image-cropper-1.4.2.tgz#ad66fbc51d526cbfb27906e9fea72cef8646a655" integrity sha512-XpfRJ+aqAkof7+T+qfdgu/QPCONXyXVVULQJftZH6snf5U6sX0Bk4SfDTEXewbPt/PRiOWLC1102w337dvTi3A== +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + react-calendar@^3.8.0: version "3.9.0" resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-3.9.0.tgz#4dfe342ef61574c0e819e49847981076c7af58ea" @@ -5310,6 +5392,16 @@ react-is@^18.0.0, react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-json-view@^1.21.3: + version "1.21.3" + resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" + integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== + dependencies: + flux "^4.0.1" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^8.3.2" + react-kapsule@2: version "2.4.0" resolved "https://registry.yarnpkg.com/react-kapsule/-/react-kapsule-2.4.0.tgz#50d296ed2872a6db89f2de176eebb1a4ce2cccb8" @@ -5318,7 +5410,7 @@ react-kapsule@2: fromentries "^1.3.2" jerrypick "^1.1.1" -react-lifecycles-compat@^3.0.0: +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -5384,6 +5476,15 @@ react-spinners@0.13.0: resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.0.tgz#2807fbd863188a2d04191fe5131f28f53b736de5" integrity sha512-CrSZSTI7hRGztt2tQtAUNLNI+9KvUG9+5KStab4R67ivyp0uR5hBOGrxKcV6sN0BSVwAPijkmlEiqRi0q/ySYg== +react-textarea-autosize@^8.3.2: + version "8.5.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== + dependencies: + "@babel/runtime" "^7.20.13" + use-composed-ref "^1.3.0" + use-latest "^1.2.1" + react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" @@ -5795,6 +5896,11 @@ set-cookie-parser@^2.4.6: resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -6674,6 +6780,11 @@ ua-parser-js@^0.7.30: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== +ua-parser-js@^1.0.35: + version "1.0.36" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c" + integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw== + unified@^10.0.0: version "10.1.2" resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df" @@ -6778,6 +6889,23 @@ url-set-query@^1.0.0: resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== +use-composed-ref@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== + +use-isomorphic-layout-effect@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + +use-latest@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== + dependencies: + use-isomorphic-layout-effect "^1.1.1" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From bfdb7f8069b5960d793365a969d6f578f50d4ce5 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 18 Oct 2023 10:59:04 +0300 Subject: [PATCH 3/3] Fixed issue with contentLength (#2742) ### What's done: - moved checking of error to controller - fixed setting contentLength --- .../cosv/controllers/RawCosvFileController.kt | 22 +++++++++++++++++-- .../cosv/storage/RawCosvFileS3KeyManager.kt | 18 +++++++++------ .../save/cosv/storage/RawCosvFileStorage.kt | 15 ------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/controllers/RawCosvFileController.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/controllers/RawCosvFileController.kt index 292710101a..8113fb8849 100644 --- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/controllers/RawCosvFileController.kt +++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/controllers/RawCosvFileController.kt @@ -13,6 +13,7 @@ import com.saveourtool.save.storage.concatS3Key import com.saveourtool.save.utils.* import com.saveourtool.save.v1 import org.reactivestreams.Publisher +import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.domain.PageRequest import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus @@ -21,9 +22,11 @@ import org.springframework.http.ResponseEntity import org.springframework.http.codec.multipart.FilePart import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.* +import org.springframework.web.server.ResponseStatusException import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers +import java.nio.ByteBuffer import java.nio.file.Files import kotlin.io.path.* @@ -115,7 +118,7 @@ class RawCosvFileController( contentLength = contentLength, ) val content = filePart.content().map { it.asByteBuffer() } - return rawCosvFileStorage.upload(key, content) + return rawCosvFileStorage.uploadAndWrapDuplicateKeyException(key, content) } /** @@ -183,7 +186,7 @@ class RawCosvFileController( "Processing ${file.absolutePathString()}" } val contentLength = file.fileSize() - rawCosvFileStorage.upload( + rawCosvFileStorage.uploadAndWrapDuplicateKeyException( key = RawCosvFileDto( concatS3Key(archiveFile.fileName, file.relativeTo(contentDir).toString()), organizationName = organizationName, @@ -414,5 +417,20 @@ class RawCosvFileController( // to show progress bar private val firstFakeResponse = UnzipRawCosvFileResponse(5, 100, updateCounters = true) + + private fun RawCosvFileStorage.uploadAndWrapDuplicateKeyException( + key: RawCosvFileDto, + content: Flux, + ): Mono { + val result = key.contentLength?.let { + upload(key, it, content) + } ?: upload(key, content) + return result.onErrorResume { error -> + when (error) { + is DataIntegrityViolationException -> Mono.error(ResponseStatusException(HttpStatus.BAD_REQUEST, "Duplicate file name ${key.fileName}", error)) + else -> Mono.error(error) + } + } + } } } diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileS3KeyManager.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileS3KeyManager.kt index 581d2dad93..02768c6bcf 100644 --- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileS3KeyManager.kt +++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileS3KeyManager.kt @@ -107,11 +107,15 @@ class RawCosvFileS3KeyManager( } @Transactional - override fun updateKeyByContentLength(key: RawCosvFileDto, contentLength: Long): RawCosvFileDto = - key.contentLength?.let { - repository.getByIdOrNotFound(key.requiredId()) - .let { entity -> - repository.save(entity.apply { this.contentLength = contentLength }).toDto() - } - } ?: key + override fun updateKeyByContentLength( + key: RawCosvFileDto, + contentLength: Long, + ): RawCosvFileDto = key.contentLength + ?.let { key } + ?: run { + repository.getByIdOrNotFound(key.requiredId()) + .let { entity -> + repository.save(entity.apply { this.contentLength = contentLength }).toDto() + } + } } diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileStorage.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileStorage.kt index a906ae9244..845e4c6d78 100644 --- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileStorage.kt +++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/storage/RawCosvFileStorage.kt @@ -10,11 +10,8 @@ import com.saveourtool.save.storage.DefaultStorageProjectReactor import com.saveourtool.save.storage.ReactiveStorageWithDatabase import com.saveourtool.save.storage.deleteUnexpectedKeys import com.saveourtool.save.utils.* -import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.domain.PageRequest -import org.springframework.http.HttpStatus import org.springframework.stereotype.Component -import org.springframework.web.server.ResponseStatusException import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.nio.ByteBuffer @@ -54,18 +51,6 @@ class RawCosvFileStorage( } .publishOn(s3Operations.scheduler) - override fun upload(key: RawCosvFileDto, content: Flux): Mono { - val result = key.contentLength?.let { - upload(key, it, content) - } ?: super.upload(key, content) - return result.onErrorResume { error -> - when (error) { - is DataIntegrityViolationException -> Mono.error(ResponseStatusException(HttpStatus.BAD_REQUEST, "Duplicate file name ${key.fileName}", error)) - else -> Mono.error(error) - } - } - } - /** * @param organizationName * @param userName