diff --git a/save-backend/src/main/kotlin/org/cqfn/save/backend/controllers/LnkUserOrganizationController.kt b/save-backend/src/main/kotlin/org/cqfn/save/backend/controllers/LnkUserOrganizationController.kt index b9213f6186..3b2722ee9e 100644 --- a/save-backend/src/main/kotlin/org/cqfn/save/backend/controllers/LnkUserOrganizationController.kt +++ b/save-backend/src/main/kotlin/org/cqfn/save/backend/controllers/LnkUserOrganizationController.kt @@ -12,6 +12,8 @@ import org.cqfn.save.backend.service.LnkUserOrganizationService import org.cqfn.save.backend.service.OrganizationService import org.cqfn.save.backend.utils.AuthenticationDetails import org.cqfn.save.domain.Role +import org.cqfn.save.entities.OrganizationStatus +import org.cqfn.save.info.OrganizationInfo import org.cqfn.save.info.UserInfo import org.cqfn.save.permission.Permission import org.cqfn.save.permission.SetRoleRequest @@ -193,6 +195,32 @@ class LnkUserOrganizationController( ) return (exactMatchUsers + prefixUsers).map { it.toUserInfo() } } + + /** + * Get not deleted organizations that are connected with current user. + * + * @param authentication + * @return Map where key is organization name, value is a pair of avatar and role. + */ + @Suppress("TYPE_ALIAS", "UnsafeCallOnNullableType") + @GetMapping("/by-user/not-deleted") + fun getOrganizationWithRoles( + authentication: Authentication, + ): List { + val selfId = (authentication.details as AuthenticationDetails).id + val user = lnkUserOrganizationService.getUserById(selfId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND) } + return lnkUserOrganizationService + .getOrganizationsAndRolesByUser(user) + .filter { it.organization?.status != OrganizationStatus.DELETED } + .map { + OrganizationInfo( + it.organization!!.name, + mapOf(user.name!! to (it.role ?: Role.NONE)), + it.organization!!.avatar, + ) + } + } companion object { const val PAGE_SIZE = 5 } diff --git a/save-backend/src/main/kotlin/org/cqfn/save/backend/repository/LnkUserOrganizationRepository.kt b/save-backend/src/main/kotlin/org/cqfn/save/backend/repository/LnkUserOrganizationRepository.kt index e9bd56571a..b5745e5c14 100644 --- a/save-backend/src/main/kotlin/org/cqfn/save/backend/repository/LnkUserOrganizationRepository.kt +++ b/save-backend/src/main/kotlin/org/cqfn/save/backend/repository/LnkUserOrganizationRepository.kt @@ -58,4 +58,10 @@ interface LnkUserOrganizationRepository : BaseEntityRepository } diff --git a/save-backend/src/main/kotlin/org/cqfn/save/backend/service/LnkUserOrganizationService.kt b/save-backend/src/main/kotlin/org/cqfn/save/backend/service/LnkUserOrganizationService.kt index 097c0a9fe9..980093cbf5 100644 --- a/save-backend/src/main/kotlin/org/cqfn/save/backend/service/LnkUserOrganizationService.kt +++ b/save-backend/src/main/kotlin/org/cqfn/save/backend/service/LnkUserOrganizationService.kt @@ -74,7 +74,7 @@ class LnkUserOrganizationService( * @param organization * @return role of the [user] in [organization] */ - @Suppress("KDOC_WITHOUT_PARAM_TAG", "UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType") fun getRole(user: User, organization: Organization) = lnkUserOrganizationRepository .findByUserIdAndOrganization(user.id!!, organization) ?.role @@ -99,7 +99,7 @@ class LnkUserOrganizationService( * @param organization * @return Unit */ - @Suppress("KDOC_WITHOUT_PARAM_TAG", "UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType") fun removeRole(user: User, organization: Organization) = lnkUserOrganizationRepository .findByUserIdAndOrganization(user.id!!, organization) ?.id @@ -148,4 +148,12 @@ class LnkUserOrganizationService( val selfOrganizationRole = findRoleByUserIdAndOrganization(selfId, organization) return getHighestRole(selfOrganizationRole, selfGlobalRole) } + + /** + * @param user + * @return [Organization]s that are connected to the [user] + */ + @Suppress("UnsafeCallOnNullableType") + fun getOrganizationsAndRolesByUser(user: User): List = + lnkUserOrganizationRepository.findByUserId(user.id!!) } diff --git a/save-cloud-common/src/commonMain/kotlin/org/cqfn/save/info/OrganizationInfo.kt b/save-cloud-common/src/commonMain/kotlin/org/cqfn/save/info/OrganizationInfo.kt new file mode 100644 index 0000000000..7aba562706 --- /dev/null +++ b/save-cloud-common/src/commonMain/kotlin/org/cqfn/save/info/OrganizationInfo.kt @@ -0,0 +1,19 @@ +package org.cqfn.save.info + +import org.cqfn.save.domain.Role + +import kotlinx.serialization.Serializable + +/** + * Represents all data related to the Organization + * + * @property name organization name + * @property userRoles map that matches usernames and roles + * @property avatar avatar of organization + */ +@Serializable +data class OrganizationInfo( + val name: String, + val userRoles: Map = emptyMap(), + val avatar: String? = null, +) diff --git a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/App.kt b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/App.kt index 8da2fff6dc..7426483dea 100644 --- a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/App.kt +++ b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/App.kt @@ -12,6 +12,7 @@ import org.cqfn.save.frontend.components.errorModalHandler import org.cqfn.save.frontend.components.topBar import org.cqfn.save.frontend.components.views.* import org.cqfn.save.frontend.components.views.usersettingsview.UserSettingsEmailMenuView +import org.cqfn.save.frontend.components.views.usersettingsview.UserSettingsOrganizationsMenuView import org.cqfn.save.frontend.components.views.usersettingsview.UserSettingsProfileMenuView import org.cqfn.save.frontend.components.views.usersettingsview.UserSettingsTokenMenuView import org.cqfn.save.frontend.externals.fontawesome.* @@ -189,6 +190,17 @@ class App : ComponentWithScope() { } } + Route { + attrs { + path = "/:user/settings/organizations" + element = buildElement { + child(UserSettingsOrganizationsMenuView::class) { + attrs.userName = state.userInfo?.name + } + } + } + } + Route { attrs { path = "/creation" diff --git a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/TopBar.kt b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/TopBar.kt index 34290f4ea8..616417d97a 100644 --- a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/TopBar.kt +++ b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/TopBar.kt @@ -8,9 +8,7 @@ package org.cqfn.save.frontend.components import org.cqfn.save.domain.Role import org.cqfn.save.frontend.components.modal.logoutModal -import org.cqfn.save.frontend.externals.fontawesome.faCog -import org.cqfn.save.frontend.externals.fontawesome.faSignOutAlt -import org.cqfn.save.frontend.externals.fontawesome.fontAwesomeIcon +import org.cqfn.save.frontend.externals.fontawesome.* import org.cqfn.save.info.UserInfo import csstype.Width @@ -208,6 +206,11 @@ fun topBar() = fc { props -> window.location.href = "#/$name/settings/email" } } + dropdownEntry(faCity, "My organizations") { + attrs.onClickFunction = { + window.location.href = "#/$name/settings/organizations" + } + } } dropdownEntry(faSignOutAlt, "Log out") { attrs.onClickFunction = { diff --git a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsOrganizationsMenuView.kt b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsOrganizationsMenuView.kt new file mode 100644 index 0000000000..147b8b5905 --- /dev/null +++ b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsOrganizationsMenuView.kt @@ -0,0 +1,49 @@ +package org.cqfn.save.frontend.components.views.usersettingsview + +import org.cqfn.save.domain.Role +import org.cqfn.save.frontend.components.basic.cardComponent +import org.cqfn.save.v1 + +import react.FC +import react.dom.* +import react.fc + +@Suppress("MISSING_KDOC_TOP_LEVEL", "TOO_LONG_FUNCTION") +class UserSettingsOrganizationsMenuView : UserSettingsView() { + override fun renderMenu(): FC = fc { props -> + child(cardComponent(isBordered = false, hasBg = true) { + div("d-sm-flex align-items-center justify-content-center mb-4 mt-4") { + h1("h3 mb-0 mt-2 text-gray-800") { + +"Organizations" + } + } + + ul(classes = "list-group list-group-flush") { + for (organizationInfo in state.selfOrganizationInfos) { + li("list-group-item") { + div("row justify-content-between align-items-center") { + div("align-items-center ml-3") { + img(classes = "avatar avatar-user width-full border color-bg-default rounded-circle") { + attrs.src = organizationInfo.avatar?.let { + "/api/$v1/avatar$it" + } ?: "img/company.svg" + attrs.height = "60" + attrs.width = "60" + } + a(classes = "ml-2", href = "#/${organizationInfo.name}") { + +organizationInfo.name + } + } + div("mr-3") { + val role = state.userInfo?.name?.let { + organizationInfo.userRoles[it] + } ?: Role.NONE + +role.formattedName + } + } + } + } + } + }) + } +} diff --git a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsView.kt b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsView.kt index 3a921a3bb2..c8d09d43bf 100644 --- a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsView.kt +++ b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/components/views/usersettingsview/UserSettingsView.kt @@ -7,12 +7,10 @@ package org.cqfn.save.frontend.components.views.usersettingsview import org.cqfn.save.domain.ImageInfo import org.cqfn.save.frontend.components.basic.InputTypes import org.cqfn.save.frontend.components.views.AbstractView -import org.cqfn.save.frontend.externals.fontawesome.faEnvelope -import org.cqfn.save.frontend.externals.fontawesome.faKey -import org.cqfn.save.frontend.externals.fontawesome.faUser -import org.cqfn.save.frontend.externals.fontawesome.fontAwesomeIcon +import org.cqfn.save.frontend.externals.fontawesome.* import org.cqfn.save.frontend.http.getUser import org.cqfn.save.frontend.utils.* +import org.cqfn.save.info.OrganizationInfo import org.cqfn.save.info.UserInfo import org.cqfn.save.utils.AvatarType import org.cqfn.save.v1 @@ -47,7 +45,7 @@ external interface UserSettingsProps : PropsWithChildren { /** * [State] of project view component */ -@Suppress("MISSING_KDOC_TOP_LEVEL") +@Suppress("MISSING_KDOC_TOP_LEVEL", "TYPE_ALIAS") external interface UserSettingsViewState : State { /** * Flag to handle uploading a file @@ -68,6 +66,11 @@ external interface UserSettingsViewState : State { * Token for user */ var token: String? + + /** + * Organizations connected to user + */ + var selfOrganizationInfos: List } @Suppress("MISSING_KDOC_TOP_LEVEL") @@ -76,6 +79,7 @@ abstract class UserSettingsView : AbstractView() } + + @Suppress("TYPE_ALIAS") + private suspend fun getOrganizationInfos() = get( + "$apiUrl/organizations/by-user/not-deleted", + Headers(), + ) + .unsafeMap { it.decodeFromJsonString>() } } diff --git a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/externals/fontawesome/Icons.kt b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/externals/fontawesome/Icons.kt index 8fed39cca0..4e44989e8d 100644 --- a/save-frontend/src/main/kotlin/org/cqfn/save/frontend/externals/fontawesome/Icons.kt +++ b/save-frontend/src/main/kotlin/org/cqfn/save/frontend/externals/fontawesome/Icons.kt @@ -39,3 +39,4 @@ external val faCog: dynamic external val faHome: dynamic external val faEnvelope: dynamic external val faKey: dynamic +external val faCity: dynamic