From 00f567aa94d5024eb7824e7c007dbcf4beda538c Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 14:04:18 +0100 Subject: [PATCH 01/77] begin with implementing backend of team management --- .../controller/OrganisationController.java | 37 +++++++++++ .../authorization/AuthorizationService.java | 9 ++- .../OrganisationAuthorizationService.java | 18 ++++++ .../business/OrganisationBusinessService.java | 6 +- .../OrganisationPersistenceService.java | 35 +++++++++- .../okr/controller/ObjectiveControllerIT.java | 2 - .../controller/OrganisationControllerIT.java | 64 +++++++++++++++++++ 7 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java create mode 100644 backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java create mode 100644 backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java new file mode 100644 index 0000000000..deab4bea9e --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -0,0 +1,37 @@ +package ch.puzzle.okr.controller; + +import ch.puzzle.okr.dto.ObjectiveDto; +import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.service.business.OrganisationBusinessService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("api/v2/organisations") +public class OrganisationController { + private final OrganisationBusinessService organisationBusinessService; + + public OrganisationController(OrganisationBusinessService organisationBusinessService) { + this.organisationBusinessService = organisationBusinessService; + } + + @Operation(summary = "Get all Organisations", description = "Get all Organisations") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Returned all Organisations", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class))}), + @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content)}) + @GetMapping + public ResponseEntity> getOrganisations() { + return ResponseEntity.status(HttpStatus.OK).body(organisationBusinessService.getOrganisations()); + } +} diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java index 2312ef4cd7..46bd5aa96f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java @@ -10,6 +10,7 @@ import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.service.persistence.ActionPersistenceService; import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; +import ch.puzzle.okr.service.persistence.OrganisationPersistenceService; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -35,14 +36,16 @@ public class AuthorizationService { private final AuthorizationRegistrationService authorizationRegistrationService; private final ObjectivePersistenceService objectivePersistenceService; private final ActionPersistenceService actionPersistenceService; + private final OrganisationPersistenceService organisationPersistenceService; private final JwtUserConverter jwtUserConverter; public AuthorizationService(AuthorizationRegistrationService authorizationRegistrationService, ObjectivePersistenceService objectivePersistenceService, ActionPersistenceService actionPersistenceService, - JwtUserConverter jwtUserConverter) { + JwtUserConverter jwtUserConverter, OrganisationPersistenceService organisationPersistenceService) { this.authorizationRegistrationService = authorizationRegistrationService; this.actionPersistenceService = actionPersistenceService; this.objectivePersistenceService = objectivePersistenceService; + this.organisationPersistenceService = organisationPersistenceService; this.jwtUserConverter = jwtUserConverter; } @@ -96,6 +99,10 @@ public void hasRoleReadByCheckInId(Long checkInId, AuthorizationUser authorizati NOT_AUTHORIZED_TO_READ_CHECK_IN); } + public void hasRoleReadByOrganisation(Long organisationId, AuthorizationUser authorizationUser) { + organisationPersistenceService.findById(organisationId, authorizationUser); + } + public void hasRoleCreateOrUpdate(Objective objective, AuthorizationUser authorizationUser) { hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_OBJECTIVE); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java new file mode 100644 index 0000000000..246079af82 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java @@ -0,0 +1,18 @@ +package ch.puzzle.okr.service.authorization; + +import ch.puzzle.okr.service.business.OrganisationBusinessService; + + +public class OrganisationAuthorizationService { + private final OrganisationBusinessService organisationBusinessService; + private final AuthorizationService authorizationService; + + public OrganisationAuthorizationService(OrganisationBusinessService organisationBusinessService, + AuthorizationService authorizationService) { + this.organisationBusinessService = organisationBusinessService; + this.authorizationService = authorizationService; + } + + + +} diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java index 9ea0663414..593b555def 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java @@ -6,7 +6,6 @@ import ch.puzzle.okr.service.persistence.OrganisationPersistenceService; import org.springframework.beans.factory.annotation.Value; import org.springframework.ldap.core.LdapTemplate; -import org.springframework.ldap.query.ContainerCriteria; import org.springframework.ldap.query.LdapQuery; import org.springframework.ldap.query.LdapQueryBuilder; import org.springframework.scheduling.annotation.Scheduled; @@ -14,7 +13,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; @Service public class OrganisationBusinessService { @@ -51,4 +49,8 @@ public void importOrgFromLDAP() { persistenceService.updateOrganisationStateToInactive(orgName); } } + + public List getOrganisations() { + return this.persistenceService.findAll(); + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java index 0c9d385d5c..3abbf8fe37 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java @@ -1,15 +1,34 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; +import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.repository.OrganisationRepository; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.TypedQuery; + +import static org.springframework.http.HttpStatus.UNAUTHORIZED; @Service public class OrganisationPersistenceService extends PersistenceBase { + private final EntityManager entityManager; + private final AuthorizationCriteria authorizationCriteria; + private static final String SELECT_ORGANISATION_BY_ID = "SELECT o FROM Organisation o WHERE o.id=:id"; - protected OrganisationPersistenceService(OrganisationRepository repository) { + protected OrganisationPersistenceService(OrganisationRepository repository, EntityManager entityManager, + AuthorizationCriteria authorizationCriteria) { super(repository); + this.entityManager = entityManager; + this.authorizationCriteria = authorizationCriteria; + } + + public Organisation findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { + return findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); } @Override @@ -17,6 +36,20 @@ public String getModelName() { return "Organisation"; } + private Organisation findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { + checkIdNull(id); + String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); + logger.debug("select objective by id={}: {}", id, fullQueryString); + TypedQuery typedQuery = entityManager.createQuery(fullQueryString, Objective.class); + typedQuery.setParameter("id", id); + authorizationCriteria.setParameters(typedQuery, authorizationUser); + try { + return typedQuery.getSingleResult(); + } catch (NoResultException exception) { + throw new ResponseStatusException(UNAUTHORIZED, reason); + } + } + public Organisation saveIfNotExists(Organisation org) { if (!getRepository().existsOrganisationByOrgName(org.getOrgName())) { return getRepository().save(org); diff --git a/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java index 48ce9d1585..f95680f5c8 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java @@ -3,7 +3,6 @@ import ch.puzzle.okr.dto.ObjectiveDto; import ch.puzzle.okr.mapper.ObjectiveMapper; import ch.puzzle.okr.models.*; -import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.service.authorization.AuthorizationService; import ch.puzzle.okr.service.authorization.ObjectiveAuthorizationService; import ch.puzzle.okr.service.business.ObjectiveBusinessService; @@ -27,7 +26,6 @@ import java.time.LocalDateTime; -import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; diff --git a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java new file mode 100644 index 0000000000..b8749310e8 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java @@ -0,0 +1,64 @@ +package ch.puzzle.okr.controller; + +import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.models.OrganisationState; +import ch.puzzle.okr.models.Team; +import ch.puzzle.okr.service.business.OrganisationBusinessService; +import org.hamcrest.core.Is; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.BDDMockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@WithMockUser(value = "spring") +@ExtendWith(MockitoExtension.class) +@WebMvcTest(OrganisationController.class) +class OrganisationControllerIT { + private static final Team PUZZLE = Team.Builder.builder().withId(1L).withName("PUZZLE ITC").build(); + private static final Organisation organisationPuzzle = Organisation.Builder.builder() + .withId(1L) + .withState(OrganisationState.ACTIVE) + .withTeams(List.of(PUZZLE)) + .withVersion(1) + .withOrgName("org_puzzle") + .build(); + + private static final Organisation organisationBBT = Organisation.Builder.builder() + .withId(1L) + .withState(OrganisationState.ACTIVE) + .withTeams(List.of(PUZZLE)) + .withVersion(1) + .withOrgName("org_bbt") + .build(); + private static final String URL_ORGANISATION = "/api/v2/organisations"; + @Autowired + private MockMvc mvc; + @MockBean + private OrganisationBusinessService organisationBusinessService; + + @Test + void shouldReturnIsOk() throws Exception { + mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @Test + void shouldReturnOrganisationsOfBusinessService() throws Exception { + BDDMockito.given(organisationBusinessService.getOrganisations()).willReturn(List.of(organisationPuzzle, organisationBBT)); + mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(jsonPath("$", Is.is(organisationPuzzle))); + } +} From 3ebc850c896f518ff6354f2075d0212d2821b932 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 14:04:38 +0100 Subject: [PATCH 02/77] format files --- .../controller/OrganisationController.java | 4 ++-- .../OrganisationAuthorizationService.java | 5 +---- .../controller/OrganisationControllerIT.java | 22 ++++++------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java index deab4bea9e..dd339a3bc6 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -28,8 +28,8 @@ public OrganisationController(OrganisationBusinessService organisationBusinessSe @Operation(summary = "Get all Organisations", description = "Get all Organisations") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned all Organisations", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class))}), - @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content)}) + @Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class)) }), + @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content) }) @GetMapping public ResponseEntity> getOrganisations() { return ResponseEntity.status(HttpStatus.OK).body(organisationBusinessService.getOrganisations()); diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java index 246079af82..e871d12cfd 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java @@ -2,17 +2,14 @@ import ch.puzzle.okr.service.business.OrganisationBusinessService; - public class OrganisationAuthorizationService { private final OrganisationBusinessService organisationBusinessService; private final AuthorizationService authorizationService; public OrganisationAuthorizationService(OrganisationBusinessService organisationBusinessService, - AuthorizationService authorizationService) { + AuthorizationService authorizationService) { this.organisationBusinessService = organisationBusinessService; this.authorizationService = authorizationService; } - - } diff --git a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java index b8749310e8..29184e4420 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java @@ -27,20 +27,12 @@ @WebMvcTest(OrganisationController.class) class OrganisationControllerIT { private static final Team PUZZLE = Team.Builder.builder().withId(1L).withName("PUZZLE ITC").build(); - private static final Organisation organisationPuzzle = Organisation.Builder.builder() - .withId(1L) - .withState(OrganisationState.ACTIVE) - .withTeams(List.of(PUZZLE)) - .withVersion(1) - .withOrgName("org_puzzle") + private static final Organisation organisationPuzzle = Organisation.Builder.builder().withId(1L) + .withState(OrganisationState.ACTIVE).withTeams(List.of(PUZZLE)).withVersion(1).withOrgName("org_puzzle") .build(); - private static final Organisation organisationBBT = Organisation.Builder.builder() - .withId(1L) - .withState(OrganisationState.ACTIVE) - .withTeams(List.of(PUZZLE)) - .withVersion(1) - .withOrgName("org_bbt") + private static final Organisation organisationBBT = Organisation.Builder.builder().withId(1L) + .withState(OrganisationState.ACTIVE).withTeams(List.of(PUZZLE)).withVersion(1).withOrgName("org_bbt") .build(); private static final String URL_ORGANISATION = "/api/v2/organisations"; @Autowired @@ -56,9 +48,9 @@ void shouldReturnIsOk() throws Exception { @Test void shouldReturnOrganisationsOfBusinessService() throws Exception { - BDDMockito.given(organisationBusinessService.getOrganisations()).willReturn(List.of(organisationPuzzle, organisationBBT)); + BDDMockito.given(organisationBusinessService.getOrganisations()) + .willReturn(List.of(organisationPuzzle, organisationBBT)); mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(jsonPath("$", Is.is(organisationPuzzle))); + .andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Is.is(organisationPuzzle))); } } From b556bc9c81fa93e004c1dae2641113bc22d8a1f7 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 15:23:36 +0100 Subject: [PATCH 03/77] implement authorization flow of organisation --- .../controller/OrganisationController.java | 10 +++++----- .../authorization/AuthorizationService.java | 4 +++- .../OrganisationAuthorizationService.java | 17 +++++++++++++++++ .../OrganisationPersistenceService.java | 19 +++++++++++-------- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java index dd339a3bc6..0e595fc61f 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -2,7 +2,7 @@ import ch.puzzle.okr.dto.ObjectiveDto; import ch.puzzle.okr.models.Organisation; -import ch.puzzle.okr.service.business.OrganisationBusinessService; +import ch.puzzle.okr.service.authorization.OrganisationAuthorizationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -19,10 +19,10 @@ @RestController @RequestMapping("api/v2/organisations") public class OrganisationController { - private final OrganisationBusinessService organisationBusinessService; + private final OrganisationAuthorizationService organisationAuthorizationService; - public OrganisationController(OrganisationBusinessService organisationBusinessService) { - this.organisationBusinessService = organisationBusinessService; + public OrganisationController(OrganisationAuthorizationService organisationAuthorizationService) { + this.organisationAuthorizationService = organisationAuthorizationService; } @Operation(summary = "Get all Organisations", description = "Get all Organisations") @@ -32,6 +32,6 @@ public OrganisationController(OrganisationBusinessService organisationBusinessSe @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content) }) @GetMapping public ResponseEntity> getOrganisations() { - return ResponseEntity.status(HttpStatus.OK).body(organisationBusinessService.getOrganisations()); + return ResponseEntity.status(HttpStatus.OK).body(organisationAuthorizationService.getEntities()); } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java index 46bd5aa96f..6e2bd1f7a2 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java @@ -26,6 +26,7 @@ public class AuthorizationService { private static final String NOT_AUTHORIZED_TO_READ_OBJECTIVE = "not authorized to read objective"; private static final String NOT_AUTHORIZED_TO_READ_KEY_RESULT = "not authorized to read key result"; private static final String NOT_AUTHORIZED_TO_READ_CHECK_IN = "not authorized to read check in"; + private static final String NOT_AUTHORIZED_TO_READ_ORGANISATION = "not authorized to read organisation"; private static final String NOT_AUTHORIZED_TO_WRITE_OBJECTIVE = "not authorized to create or update objective"; private static final String NOT_AUTHORIZED_TO_WRITE_KEY_RESULT = "not authorized to create or update key result"; private static final String NOT_AUTHORIZED_TO_WRITE_CHECK_IN = "not authorized to create or update check in"; @@ -100,7 +101,8 @@ public void hasRoleReadByCheckInId(Long checkInId, AuthorizationUser authorizati } public void hasRoleReadByOrganisation(Long organisationId, AuthorizationUser authorizationUser) { - organisationPersistenceService.findById(organisationId, authorizationUser); + organisationPersistenceService.findOrganisationById(organisationId, authorizationUser, + NOT_AUTHORIZED_TO_READ_ORGANISATION); } public void hasRoleCreateOrUpdate(Objective objective, AuthorizationUser authorizationUser) { diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java index e871d12cfd..2585546792 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java @@ -1,7 +1,13 @@ package ch.puzzle.okr.service.authorization; +import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.service.business.OrganisationBusinessService; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service public class OrganisationAuthorizationService { private final OrganisationBusinessService organisationBusinessService; private final AuthorizationService authorizationService; @@ -12,4 +18,15 @@ public OrganisationAuthorizationService(OrganisationBusinessService organisation this.authorizationService = authorizationService; } + public List getEntities() { + AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser(); + List organisations = organisationBusinessService.getOrganisations(); + organisations.forEach(organisation -> hasRoleReadById(organisation, authorizationUser)); + return organisations; + } + + protected void hasRoleReadById(Organisation entity, AuthorizationUser authorizationUser) { + authorizationService.hasRoleReadByOrganisation(entity.getId(), authorizationUser); + } + } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java index 3abbf8fe37..43a74d511b 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java @@ -1,10 +1,11 @@ package ch.puzzle.okr.service.persistence; -import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.repository.OrganisationRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -16,9 +17,11 @@ @Service public class OrganisationPersistenceService extends PersistenceBase { + + private static final Logger logger = LoggerFactory.getLogger(OrganisationPersistenceService.class); + private static final String SELECT_ORGANISATION_BY_ID = "SELECT o FROM Organisation o WHERE o.id=:id"; private final EntityManager entityManager; private final AuthorizationCriteria authorizationCriteria; - private static final String SELECT_ORGANISATION_BY_ID = "SELECT o FROM Organisation o WHERE o.id=:id"; protected OrganisationPersistenceService(OrganisationRepository repository, EntityManager entityManager, AuthorizationCriteria authorizationCriteria) { @@ -27,20 +30,20 @@ protected OrganisationPersistenceService(OrganisationRepository repository, Enti this.authorizationCriteria = authorizationCriteria; } - public Organisation findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); - } - @Override public String getModelName() { return "Organisation"; } + public Organisation findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { + return findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); + } + private Organisation findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { checkIdNull(id); String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); - logger.debug("select objective by id={}: {}", id, fullQueryString); - TypedQuery typedQuery = entityManager.createQuery(fullQueryString, Objective.class); + logger.debug("select organisation by id={}: {}", id, fullQueryString); + TypedQuery typedQuery = entityManager.createQuery(fullQueryString, Organisation.class); typedQuery.setParameter("id", id); authorizationCriteria.setParameters(typedQuery, authorizationUser); try { From 3d4ad10cc6ec8c63cc0c638dbab3f316f3d3760b Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 15:27:12 +0100 Subject: [PATCH 04/77] replace organisation business service with organisation authorization service --- .../ch/puzzle/okr/controller/OrganisationControllerIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java index 29184e4420..1cb54de093 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java @@ -3,7 +3,7 @@ import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; import ch.puzzle.okr.models.Team; -import ch.puzzle.okr.service.business.OrganisationBusinessService; +import ch.puzzle.okr.service.authorization.OrganisationAuthorizationService; import org.hamcrest.core.Is; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,7 +38,7 @@ class OrganisationControllerIT { @Autowired private MockMvc mvc; @MockBean - private OrganisationBusinessService organisationBusinessService; + private OrganisationAuthorizationService organisationAuthorizationService; @Test void shouldReturnIsOk() throws Exception { @@ -48,7 +48,7 @@ void shouldReturnIsOk() throws Exception { @Test void shouldReturnOrganisationsOfBusinessService() throws Exception { - BDDMockito.given(organisationBusinessService.getOrganisations()) + BDDMockito.given(organisationAuthorizationService.getEntities()) .willReturn(List.of(organisationPuzzle, organisationBBT)); mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Is.is(organisationPuzzle))); From f301fb79b202353d9425c577e5fc69b38f48653b Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 15:56:24 +0100 Subject: [PATCH 05/77] implement mapper and dtos in controller --- .../controller/OrganisationController.java | 13 ++++--- .../ch/puzzle/okr/dto/OrganisationDto.java | 8 +++++ .../puzzle/okr/mapper/OrganisationMapper.java | 36 +++++++++++++++++++ .../OrganisationPersistenceService.java | 8 ++--- 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/ch/puzzle/okr/dto/OrganisationDto.java create mode 100644 backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java index 0e595fc61f..7f72873202 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -1,7 +1,8 @@ package ch.puzzle.okr.controller; import ch.puzzle.okr.dto.ObjectiveDto; -import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.dto.OrganisationDto; +import ch.puzzle.okr.mapper.OrganisationMapper; import ch.puzzle.okr.service.authorization.OrganisationAuthorizationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -20,9 +21,12 @@ @RequestMapping("api/v2/organisations") public class OrganisationController { private final OrganisationAuthorizationService organisationAuthorizationService; + private final OrganisationMapper organisationMapper; - public OrganisationController(OrganisationAuthorizationService organisationAuthorizationService) { + public OrganisationController(OrganisationAuthorizationService organisationAuthorizationService, + OrganisationMapper organisationMapper) { this.organisationAuthorizationService = organisationAuthorizationService; + this.organisationMapper = organisationMapper; } @Operation(summary = "Get all Organisations", description = "Get all Organisations") @@ -31,7 +35,8 @@ public OrganisationController(OrganisationAuthorizationService organisationAutho @Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class)) }), @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content) }) @GetMapping - public ResponseEntity> getOrganisations() { - return ResponseEntity.status(HttpStatus.OK).body(organisationAuthorizationService.getEntities()); + public ResponseEntity> getOrganisations() { + return ResponseEntity.status(HttpStatus.OK).body( + organisationAuthorizationService.getEntities().stream().map(this.organisationMapper::toDto).toList()); } } diff --git a/backend/src/main/java/ch/puzzle/okr/dto/OrganisationDto.java b/backend/src/main/java/ch/puzzle/okr/dto/OrganisationDto.java new file mode 100644 index 0000000000..97432a551d --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/dto/OrganisationDto.java @@ -0,0 +1,8 @@ +package ch.puzzle.okr.dto; + +import ch.puzzle.okr.models.OrganisationState; + +import java.util.List; + +public record OrganisationDto(Long id, int version, String orgName, List teams, OrganisationState state) { +} diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java new file mode 100644 index 0000000000..a7eaf3a679 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java @@ -0,0 +1,36 @@ +package ch.puzzle.okr.mapper; + +import ch.puzzle.okr.dto.OrganisationDto; +import ch.puzzle.okr.dto.TeamDto; +import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.models.Team; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class OrganisationMapper { + private final TeamMapper teamMapper; + + public OrganisationMapper(TeamMapper teamMapper) { + this.teamMapper = teamMapper; + } + + public OrganisationDto toDto(Organisation organisation) { + List teamDtoList = organisation.getTeams().stream().map(team -> teamMapper.toDto(team, null)) + .toList(); + return new OrganisationDto(organisation.getId(), organisation.getVersion(), organisation.getOrgName(), + teamDtoList, organisation.getState()); + } + + public Organisation toOrganisation(OrganisationDto organisationDto) { + List teams = organisationDto.teams().stream().map(this.teamMapper::toTeam).toList(); + return Organisation.Builder.builder() + .withId(organisationDto.id()) + .withVersion(organisationDto.version()) + .withOrgName(organisationDto.orgName()) + .withTeams(teams) + .withState(organisationDto.state()) + .build(); + } +} diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java index 43a74d511b..2c67614c1f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java @@ -35,11 +35,11 @@ public String getModelName() { return "Organisation"; } - public Organisation findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); + public void findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { + findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); } - private Organisation findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { + private void findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { checkIdNull(id); String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); logger.debug("select organisation by id={}: {}", id, fullQueryString); @@ -47,7 +47,7 @@ private Organisation findByAnyId(Long id, AuthorizationUser authorizationUser, S typedQuery.setParameter("id", id); authorizationCriteria.setParameters(typedQuery, authorizationUser); try { - return typedQuery.getSingleResult(); + typedQuery.getSingleResult(); } catch (NoResultException exception) { throw new ResponseStatusException(UNAUTHORIZED, reason); } From 935ee397d808aae0dba1db18e4d3e839d06b69bc Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 15:56:43 +0100 Subject: [PATCH 06/77] format mapper --- .../ch/puzzle/okr/mapper/OrganisationMapper.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java index a7eaf3a679..359c06b9e3 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java @@ -17,20 +17,14 @@ public OrganisationMapper(TeamMapper teamMapper) { } public OrganisationDto toDto(Organisation organisation) { - List teamDtoList = organisation.getTeams().stream().map(team -> teamMapper.toDto(team, null)) - .toList(); + List teamDtoList = organisation.getTeams().stream().map(team -> teamMapper.toDto(team, null)).toList(); return new OrganisationDto(organisation.getId(), organisation.getVersion(), organisation.getOrgName(), teamDtoList, organisation.getState()); } public Organisation toOrganisation(OrganisationDto organisationDto) { List teams = organisationDto.teams().stream().map(this.teamMapper::toTeam).toList(); - return Organisation.Builder.builder() - .withId(organisationDto.id()) - .withVersion(organisationDto.version()) - .withOrgName(organisationDto.orgName()) - .withTeams(teams) - .withState(organisationDto.state()) - .build(); + return Organisation.Builder.builder().withId(organisationDto.id()).withVersion(organisationDto.version()) + .withOrgName(organisationDto.orgName()).withTeams(teams).withState(organisationDto.state()).build(); } } From 44d90c6c9b9230658502bf3f037041a67129c385 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 16:15:18 +0100 Subject: [PATCH 07/77] write first test to check whether controller return organisations correctly --- .../controller/OrganisationControllerIT.java | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java index 1cb54de093..dccb047bc2 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java @@ -1,10 +1,14 @@ package ch.puzzle.okr.controller; +import ch.puzzle.okr.dto.OrganisationDto; +import ch.puzzle.okr.dto.TeamDto; +import ch.puzzle.okr.mapper.OrganisationMapper; import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.service.authorization.OrganisationAuthorizationService; import org.hamcrest.core.Is; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.BDDMockito; @@ -26,31 +30,48 @@ @ExtendWith(MockitoExtension.class) @WebMvcTest(OrganisationController.class) class OrganisationControllerIT { + + /* Team test objects */ private static final Team PUZZLE = Team.Builder.builder().withId(1L).withName("PUZZLE ITC").build(); + private static final Team BBT = Team.Builder.builder().withId(1L).withName("/BBT").build(); + private static final TeamDto PUZZLE_DTO = new TeamDto(1L, "PUZZLE ITC", 3); + private static final TeamDto BBT_DTO = new TeamDto(1L, "/BBT", 7); + + /* Organisation test objects */ private static final Organisation organisationPuzzle = Organisation.Builder.builder().withId(1L) .withState(OrganisationState.ACTIVE).withTeams(List.of(PUZZLE)).withVersion(1).withOrgName("org_puzzle") .build(); private static final Organisation organisationBBT = Organisation.Builder.builder().withId(1L) - .withState(OrganisationState.ACTIVE).withTeams(List.of(PUZZLE)).withVersion(1).withOrgName("org_bbt") - .build(); + .withState(OrganisationState.ACTIVE).withTeams(List.of(BBT)).withVersion(1).withOrgName("org_bbt").build(); + + private static final OrganisationDto organisationPuzzleDto = new OrganisationDto(1L, 1, "org_puzzle", + List.of(PUZZLE_DTO), OrganisationState.ACTIVE); + + private static final OrganisationDto organisationBBTDto = new OrganisationDto(1L, 1, "org_bbt", List.of(BBT_DTO), + OrganisationState.ACTIVE); private static final String URL_ORGANISATION = "/api/v2/organisations"; @Autowired private MockMvc mvc; @MockBean private OrganisationAuthorizationService organisationAuthorizationService; + @MockBean + private OrganisationMapper organisationMapper; - @Test - void shouldReturnIsOk() throws Exception { - mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.status().isOk()); + @BeforeEach + void setUp() { + BDDMockito.given(organisationMapper.toDto(organisationPuzzle)).willReturn(organisationPuzzleDto); + BDDMockito.given(organisationMapper.toDto(organisationBBT)).willReturn(organisationBBTDto); } @Test void shouldReturnOrganisationsOfBusinessService() throws Exception { BDDMockito.given(organisationAuthorizationService.getEntities()) .willReturn(List.of(organisationPuzzle, organisationBBT)); + mvc.perform(get(URL_ORGANISATION).contentType(MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Is.is(organisationPuzzle))); + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(jsonPath("$[0].orgName", Is.is(organisationPuzzleDto.orgName()))) + .andExpect(jsonPath("$[1].orgName", Is.is(organisationBBTDto.orgName()))); } } From d81bd402d71dfdcda92955456b0b1ebcbad03a01 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 16:50:05 +0100 Subject: [PATCH 08/77] implement dialog which opens on click to header section --- frontend/src/app/app.module.ts | 2 ++ .../application-top-bar.component.scss | 9 +++++++++ .../application-top-bar.component.ts | 14 ++++++++++++++ .../team-management/team-management.component.css | 0 .../team-management.component.cy.ts | 7 +++++++ .../team-management/team-management.component.html | 7 +++++++ .../team-management/team-management.component.ts | 11 +++++++++++ 7 files changed, 50 insertions(+) create mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.css create mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts create mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.html create mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.ts diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 19a4ffed89..7a6b98b188 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -71,6 +71,7 @@ import { DialogHeaderComponent } from './shared/custom/dialog-header/dialog-head import { ObjectiveFilterComponent } from './objective-filter/objective-filter.component'; import { ActionPlanComponent } from './action-plan/action-plan.component'; import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; +import { TeamManagementComponent } from './shared/dialog/team-management/team-management.component'; function initOauthFactory(configService: ConfigService, oauthService: OAuthService) { return async () => { @@ -135,6 +136,7 @@ export const MY_FORMATS = { DialogHeaderComponent, ObjectiveFilterComponent, ActionPlanComponent, + TeamManagementComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.scss b/frontend/src/app/application-top-bar/application-top-bar.component.scss index 2a9bdc45a1..0985986d51 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.scss +++ b/frontend/src/app/application-top-bar/application-top-bar.component.scss @@ -36,3 +36,12 @@ background-color: #1a4e83; } } + +.header-icon { + color: white; +} + +.header-text:hover { + text-decoration: underline; + color: white; +} diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 9115d1661e..53182405b7 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -2,6 +2,9 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { map, ReplaySubject } from 'rxjs'; import { ConfigService } from '../config.service'; +import { MatDialog } from '@angular/material/dialog'; +import { TeamManagementComponent } from '../shared/dialog/team-management/team-management.component'; + import { Router } from '@angular/router'; @Component({ selector: 'app-application-top-bar', @@ -16,6 +19,7 @@ export class ApplicationTopBarComponent implements OnInit { constructor( private oauthService: OAuthService, private configService: ConfigService, + private dialog: MatDialog, private router: Router, ) {} @@ -40,4 +44,14 @@ export class ApplicationTopBarComponent implements OnInit { this.oauthService.logOut(); }); } + + openTeamManagement() { + const dialog = this.dialog.open(TeamManagementComponent, { + width: '45em', + height: 'auto', + }); + dialog.afterClosed().subscribe(() => { + console.log('In after Closed'); + }); + } } diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.css b/frontend/src/app/shared/dialog/team-management/team-management.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts new file mode 100644 index 0000000000..faa891c671 --- /dev/null +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts @@ -0,0 +1,7 @@ +import { TeamManagementComponent } from './team-management.component' + +describe('TeamManagementComponent', () => { + it('should mount', () => { + cy.mount(TeamManagementComponent) + }) +}) \ No newline at end of file diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.html b/frontend/src/app/shared/dialog/team-management/team-management.component.html new file mode 100644 index 0000000000..f683d56007 --- /dev/null +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.html @@ -0,0 +1,7 @@ +
+ +
+
+
+
+
diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.ts new file mode 100644 index 0000000000..688fe11740 --- /dev/null +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-team-management', + templateUrl: './team-management.component.html', + styleUrls: ['./team-management.component.css'] +}) +export class TeamManagementComponent { + constructor(public dialogRef: MatDialogRef, private dialog: MatDialog) {} +} From 337edbf91c0a2e116158dc2724aab8602f9026fe Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 16:50:37 +0100 Subject: [PATCH 09/77] format newly generated dialog --- .../team-management/team-management.component.cy.ts | 8 ++++---- .../team-management/team-management.component.html | 5 ++--- .../team-management/team-management.component.ts | 11 +++++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts index faa891c671..6810ff87c3 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.cy.ts @@ -1,7 +1,7 @@ -import { TeamManagementComponent } from './team-management.component' +import { TeamManagementComponent } from './team-management.component'; describe('TeamManagementComponent', () => { it('should mount', () => { - cy.mount(TeamManagementComponent) - }) -}) \ No newline at end of file + cy.mount(TeamManagementComponent); + }); +}); diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.html b/frontend/src/app/shared/dialog/team-management/team-management.component.html index f683d56007..e34bc88465 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.html +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.html @@ -1,7 +1,6 @@
- +
-
-
+
diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.ts index 688fe11740..4dbfa9341f 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.ts +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.ts @@ -1,11 +1,14 @@ -import {Component} from '@angular/core'; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import { Component } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-team-management', templateUrl: './team-management.component.html', - styleUrls: ['./team-management.component.css'] + styleUrls: ['./team-management.component.css'], }) export class TeamManagementComponent { - constructor(public dialogRef: MatDialogRef, private dialog: MatDialog) {} + constructor( + public dialogRef: MatDialogRef, + private dialog: MatDialog, + ) {} } From 955706977efb644b2d89744fa25faaf4bcaf4ca9 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 16:53:09 +0100 Subject: [PATCH 10/77] fix tests by adding missing import of matdialog module --- .../application-top-bar/application-top-bar.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.spec.ts b/frontend/src/app/application-top-bar/application-top-bar.component.spec.ts index ff0cb34e3a..3a51b7b1b2 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.spec.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.spec.ts @@ -9,6 +9,7 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatMenuHarness } from '@angular/material/menu/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import {MatDialogModule} from "@angular/material/dialog"; const oAuthMock = { getIdentityClaims: jest.fn(), @@ -23,7 +24,7 @@ describe('ApplicationHeaderComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatMenuModule, NoopAnimationsModule], + imports: [MatMenuModule, NoopAnimationsModule, MatDialogModule], declarations: [ApplicationTopBarComponent], providers: [ { provide: OAuthService, useValue: oAuthMock }, From 1a50a5f64a79e1113afda28df4b9e416bcd757e2 Mon Sep 17 00:00:00 2001 From: megli2 Date: Tue, 7 Nov 2023 17:01:16 +0100 Subject: [PATCH 11/77] fix problem of multiple dialogs opening by storing dialogRef --- .../application-top-bar.component.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 53182405b7..aceb7759c2 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { map, ReplaySubject } from 'rxjs'; import { ConfigService } from '../config.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TeamManagementComponent } from '../shared/dialog/team-management/team-management.component'; import { Router } from '@angular/router'; @@ -16,6 +16,7 @@ export class ApplicationTopBarComponent implements OnInit { username: ReplaySubject = new ReplaySubject(); menuIsOpen = false; + private dialogRef!: MatDialogRef | undefined; constructor( private oauthService: OAuthService, private configService: ConfigService, @@ -46,12 +47,14 @@ export class ApplicationTopBarComponent implements OnInit { } openTeamManagement() { - const dialog = this.dialog.open(TeamManagementComponent, { - width: '45em', - height: 'auto', - }); - dialog.afterClosed().subscribe(() => { - console.log('In after Closed'); - }); + if (!this.dialogRef) { + this.dialogRef = this.dialog.open(TeamManagementComponent, { + width: '45em', + height: 'auto', + }); + this.dialogRef.afterClosed().subscribe(() => { + this.dialogRef = undefined; + }); + } } } From f4c8a81055641fa86967d8a3281d587e6ac91a0f Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 10:17:47 +0100 Subject: [PATCH 12/77] rearrange structure of backend validation of organisations and implement frontend service to call backend --- .../controller/OrganisationController.java | 2 +- .../authorization/AuthorizationService.java | 11 +----- .../OrganisationAuthorizationService.java | 15 ++++---- .../OrganisationPersistenceService.java | 38 +------------------ .../controller/OrganisationControllerIT.java | 2 +- .../team-management.component.html | 32 +++++++++++++++- .../team-management.component.ts | 33 +++++++++++++++- .../services/organisation.service.spec.ts | 16 ++++++++ .../shared/services/organisation.service.ts | 16 ++++++++ .../shared/types/enums/OrganisationState.ts | 4 ++ .../app/shared/types/model/Organisation.ts | 10 +++++ 11 files changed, 119 insertions(+), 60 deletions(-) create mode 100644 frontend/src/app/shared/services/organisation.service.spec.ts create mode 100644 frontend/src/app/shared/services/organisation.service.ts create mode 100644 frontend/src/app/shared/types/enums/OrganisationState.ts create mode 100644 frontend/src/app/shared/types/model/Organisation.ts diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java index 7f72873202..74972d8922 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -18,7 +18,7 @@ import java.util.List; @RestController -@RequestMapping("api/v2/organisations") +@RequestMapping("api/v1/organisations") public class OrganisationController { private final OrganisationAuthorizationService organisationAuthorizationService; private final OrganisationMapper organisationMapper; diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java index 6e2bd1f7a2..2312ef4cd7 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java @@ -10,7 +10,6 @@ import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.service.persistence.ActionPersistenceService; import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; -import ch.puzzle.okr.service.persistence.OrganisationPersistenceService; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -26,7 +25,6 @@ public class AuthorizationService { private static final String NOT_AUTHORIZED_TO_READ_OBJECTIVE = "not authorized to read objective"; private static final String NOT_AUTHORIZED_TO_READ_KEY_RESULT = "not authorized to read key result"; private static final String NOT_AUTHORIZED_TO_READ_CHECK_IN = "not authorized to read check in"; - private static final String NOT_AUTHORIZED_TO_READ_ORGANISATION = "not authorized to read organisation"; private static final String NOT_AUTHORIZED_TO_WRITE_OBJECTIVE = "not authorized to create or update objective"; private static final String NOT_AUTHORIZED_TO_WRITE_KEY_RESULT = "not authorized to create or update key result"; private static final String NOT_AUTHORIZED_TO_WRITE_CHECK_IN = "not authorized to create or update check in"; @@ -37,16 +35,14 @@ public class AuthorizationService { private final AuthorizationRegistrationService authorizationRegistrationService; private final ObjectivePersistenceService objectivePersistenceService; private final ActionPersistenceService actionPersistenceService; - private final OrganisationPersistenceService organisationPersistenceService; private final JwtUserConverter jwtUserConverter; public AuthorizationService(AuthorizationRegistrationService authorizationRegistrationService, ObjectivePersistenceService objectivePersistenceService, ActionPersistenceService actionPersistenceService, - JwtUserConverter jwtUserConverter, OrganisationPersistenceService organisationPersistenceService) { + JwtUserConverter jwtUserConverter) { this.authorizationRegistrationService = authorizationRegistrationService; this.actionPersistenceService = actionPersistenceService; this.objectivePersistenceService = objectivePersistenceService; - this.organisationPersistenceService = organisationPersistenceService; this.jwtUserConverter = jwtUserConverter; } @@ -100,11 +96,6 @@ public void hasRoleReadByCheckInId(Long checkInId, AuthorizationUser authorizati NOT_AUTHORIZED_TO_READ_CHECK_IN); } - public void hasRoleReadByOrganisation(Long organisationId, AuthorizationUser authorizationUser) { - organisationPersistenceService.findOrganisationById(organisationId, authorizationUser, - NOT_AUTHORIZED_TO_READ_ORGANISATION); - } - public void hasRoleCreateOrUpdate(Objective objective, AuthorizationUser authorizationUser) { hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_OBJECTIVE); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java index 2585546792..c098623fcc 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java @@ -1,9 +1,12 @@ package ch.puzzle.okr.service.authorization; import ch.puzzle.okr.models.Organisation; +import ch.puzzle.okr.models.authorization.AuthorizationRole; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.service.business.OrganisationBusinessService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import java.util.List; @@ -20,13 +23,9 @@ public OrganisationAuthorizationService(OrganisationBusinessService organisation public List getEntities() { AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser(); - List organisations = organisationBusinessService.getOrganisations(); - organisations.forEach(organisation -> hasRoleReadById(organisation, authorizationUser)); - return organisations; + if (!authorizationUser.roles().contains(AuthorizationRole.WRITE_ALL)) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "not authorized to read organisations"); + } + return organisationBusinessService.getOrganisations(); } - - protected void hasRoleReadById(Organisation entity, AuthorizationUser authorizationUser) { - authorizationService.hasRoleReadByOrganisation(entity.getId(), authorizationUser); - } - } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java index 2c67614c1f..0c9d385d5c 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceService.java @@ -2,32 +2,14 @@ import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; -import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.repository.OrganisationRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import javax.persistence.EntityManager; -import javax.persistence.NoResultException; -import javax.persistence.TypedQuery; - -import static org.springframework.http.HttpStatus.UNAUTHORIZED; @Service public class OrganisationPersistenceService extends PersistenceBase { - private static final Logger logger = LoggerFactory.getLogger(OrganisationPersistenceService.class); - private static final String SELECT_ORGANISATION_BY_ID = "SELECT o FROM Organisation o WHERE o.id=:id"; - private final EntityManager entityManager; - private final AuthorizationCriteria authorizationCriteria; - - protected OrganisationPersistenceService(OrganisationRepository repository, EntityManager entityManager, - AuthorizationCriteria authorizationCriteria) { + protected OrganisationPersistenceService(OrganisationRepository repository) { super(repository); - this.entityManager = entityManager; - this.authorizationCriteria = authorizationCriteria; } @Override @@ -35,24 +17,6 @@ public String getModelName() { return "Organisation"; } - public void findOrganisationById(Long organisationId, AuthorizationUser authorizationUser, String reason) { - findByAnyId(organisationId, authorizationUser, SELECT_ORGANISATION_BY_ID, reason); - } - - private void findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { - checkIdNull(id); - String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); - logger.debug("select organisation by id={}: {}", id, fullQueryString); - TypedQuery typedQuery = entityManager.createQuery(fullQueryString, Organisation.class); - typedQuery.setParameter("id", id); - authorizationCriteria.setParameters(typedQuery, authorizationUser); - try { - typedQuery.getSingleResult(); - } catch (NoResultException exception) { - throw new ResponseStatusException(UNAUTHORIZED, reason); - } - } - public Organisation saveIfNotExists(Organisation org) { if (!getRepository().existsOrganisationByOrgName(org.getOrgName())) { return getRepository().save(org); diff --git a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java index dccb047bc2..80812e1c37 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/OrganisationControllerIT.java @@ -50,7 +50,7 @@ class OrganisationControllerIT { private static final OrganisationDto organisationBBTDto = new OrganisationDto(1L, 1, "org_bbt", List.of(BBT_DTO), OrganisationState.ACTIVE); - private static final String URL_ORGANISATION = "/api/v2/organisations"; + private static final String URL_ORGANISATION = "/api/v1/organisations"; @Autowired private MockMvc mvc; @MockBean diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.html b/frontend/src/app/shared/dialog/team-management/team-management.component.html index e34bc88465..3852cd2350 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.html +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.html @@ -1,6 +1,36 @@
-
+
+
+ + + + + {{ errorMessages[errorKey.toUpperCase()] }} + + +
+
+ + + {{ + organisation.orgName + }} + +
+ + {{ errorMessages[errorKey.toUpperCase()] }} + +
+
+
diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.ts index 4dbfa9341f..5aea2985f5 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.ts +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.ts @@ -1,14 +1,43 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { formInputCheck } from '../../common'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import errorMessages from '../../../../assets/errors/error-messages.json'; +import { Unit } from '../../types/enums/Unit'; +import { Organisation } from '../../types/model/Organisation'; +import { OrganisationService } from '../../services/organisation.service'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-team-management', templateUrl: './team-management.component.html', styleUrls: ['./team-management.component.css'], }) -export class TeamManagementComponent { +export class TeamManagementComponent implements OnInit { + teamForm = new FormGroup({ + name: new FormControl('', [Validators.required, Validators.minLength(2), Validators.maxLength(250)]), + organisations: new FormControl([], [Validators.required]), + }); + organisations$: Observable = new Observable(); + protected readonly formInputCheck = formInputCheck; + protected readonly errorMessages: any = errorMessages; + constructor( public dialogRef: MatDialogRef, private dialog: MatDialog, + private organisationService: OrganisationService, ) {} + + ngOnInit(): void { + this.organisations$ = this.organisationService.getOrganisations(); + } + + isTouchedOrDirty(name: string) { + return this.teamForm.get(name)?.dirty || this.teamForm.get(name)?.touched; + } + + getErrorKeysOfFormField(name: string) { + const errors = this.teamForm.get(name)?.errors; + return errors == null ? [] : Object.keys(errors); + } } diff --git a/frontend/src/app/shared/services/organisation.service.spec.ts b/frontend/src/app/shared/services/organisation.service.spec.ts new file mode 100644 index 0000000000..02e05801c2 --- /dev/null +++ b/frontend/src/app/shared/services/organisation.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { OrganisationService } from './organisation.service'; + +describe('OrganisationService', () => { + let service: OrganisationService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(OrganisationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/shared/services/organisation.service.ts b/frontend/src/app/shared/services/organisation.service.ts new file mode 100644 index 0000000000..5a5a56983a --- /dev/null +++ b/frontend/src/app/shared/services/organisation.service.ts @@ -0,0 +1,16 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Organisation} from "../types/model/Organisation"; +import {Observable} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class OrganisationService { + + constructor(private http: HttpClient) { } + + getOrganisations(): Observable { + return this.http.get('/api/v1/organisations'); + } +} diff --git a/frontend/src/app/shared/types/enums/OrganisationState.ts b/frontend/src/app/shared/types/enums/OrganisationState.ts new file mode 100644 index 0000000000..91b47177f5 --- /dev/null +++ b/frontend/src/app/shared/types/enums/OrganisationState.ts @@ -0,0 +1,4 @@ +export enum OrganisationState { + ACTIVE, + INACTIVE +} diff --git a/frontend/src/app/shared/types/model/Organisation.ts b/frontend/src/app/shared/types/model/Organisation.ts new file mode 100644 index 0000000000..cc65311105 --- /dev/null +++ b/frontend/src/app/shared/types/model/Organisation.ts @@ -0,0 +1,10 @@ +import {Team} from "./Team"; +import {OrganisationState} from "../enums/OrganisationState"; + +export interface Organisation { + id: number; + version: number; + orgName: String; + teams: Team[]; + state: OrganisationState; +} From 90aa298c5ba4bcd8ed21edb654e64da2bf38a0f4 Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 10:18:11 +0100 Subject: [PATCH 13/77] format new models and service --- .../src/app/shared/services/organisation.service.ts | 13 ++++++------- .../src/app/shared/types/enums/OrganisationState.ts | 2 +- frontend/src/app/shared/types/model/Organisation.ts | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/shared/services/organisation.service.ts b/frontend/src/app/shared/services/organisation.service.ts index 5a5a56983a..6e0c1659d8 100644 --- a/frontend/src/app/shared/services/organisation.service.ts +++ b/frontend/src/app/shared/services/organisation.service.ts @@ -1,14 +1,13 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from "@angular/common/http"; -import {Organisation} from "../types/model/Organisation"; -import {Observable} from "rxjs"; +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Organisation } from '../types/model/Organisation'; +import { Observable } from 'rxjs'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class OrganisationService { - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient) {} getOrganisations(): Observable { return this.http.get('/api/v1/organisations'); diff --git a/frontend/src/app/shared/types/enums/OrganisationState.ts b/frontend/src/app/shared/types/enums/OrganisationState.ts index 91b47177f5..524ca48006 100644 --- a/frontend/src/app/shared/types/enums/OrganisationState.ts +++ b/frontend/src/app/shared/types/enums/OrganisationState.ts @@ -1,4 +1,4 @@ export enum OrganisationState { ACTIVE, - INACTIVE + INACTIVE, } diff --git a/frontend/src/app/shared/types/model/Organisation.ts b/frontend/src/app/shared/types/model/Organisation.ts index cc65311105..5c88b4e3e7 100644 --- a/frontend/src/app/shared/types/model/Organisation.ts +++ b/frontend/src/app/shared/types/model/Organisation.ts @@ -1,5 +1,5 @@ -import {Team} from "./Team"; -import {OrganisationState} from "../enums/OrganisationState"; +import { Team } from './Team'; +import { OrganisationState } from '../enums/OrganisationState'; export interface Organisation { id: number; From ac96a2069a3ebc8fa08215597e679816ffc9390d Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 10:43:45 +0100 Subject: [PATCH 14/77] fix backend call by changing fetchType --- .../main/java/ch/puzzle/okr/mapper/OrganisationMapper.java | 7 ++++--- .../src/main/java/ch/puzzle/okr/models/Organisation.java | 2 +- .../okr/service/business/OrganisationBusinessService.java | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java index 359c06b9e3..55fd934dc4 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OrganisationMapper.java @@ -17,9 +17,10 @@ public OrganisationMapper(TeamMapper teamMapper) { } public OrganisationDto toDto(Organisation organisation) { - List teamDtoList = organisation.getTeams().stream().map(team -> teamMapper.toDto(team, null)).toList(); - return new OrganisationDto(organisation.getId(), organisation.getVersion(), organisation.getOrgName(), - teamDtoList, organisation.getState()); + List teamDTOs = organisation.getTeams().stream().map(team -> this.teamMapper.toDto(team, null)) + .toList(); + return new OrganisationDto(organisation.getId(), organisation.getVersion(), organisation.getOrgName(), teamDTOs, + organisation.getState()); } public Organisation toOrganisation(OrganisationDto organisationDto) { diff --git a/backend/src/main/java/ch/puzzle/okr/models/Organisation.java b/backend/src/main/java/ch/puzzle/okr/models/Organisation.java index b033804d51..a2c6937e92 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Organisation.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Organisation.java @@ -20,7 +20,7 @@ public class Organisation { @NotBlank private String orgName; - @ManyToMany(mappedBy = "authorizationOrganisation") + @ManyToMany(mappedBy = "authorizationOrganisation", fetch = FetchType.EAGER) private List teams; @Enumerated(EnumType.STRING) diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java index 593b555def..c47d99f3f8 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/OrganisationBusinessService.java @@ -51,6 +51,6 @@ public void importOrgFromLDAP() { } public List getOrganisations() { - return this.persistenceService.findAll(); + return persistenceService.findAll(); } } From 0687508e494e4c57258b2640293534b05ec88748 Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 10:45:55 +0100 Subject: [PATCH 15/77] fix frontend service test by inserting httpclienttesting module --- .../src/app/shared/services/organisation.service.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/services/organisation.service.spec.ts b/frontend/src/app/shared/services/organisation.service.spec.ts index 02e05801c2..ade83dc206 100644 --- a/frontend/src/app/shared/services/organisation.service.spec.ts +++ b/frontend/src/app/shared/services/organisation.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from '@angular/core/testing'; import { OrganisationService } from './organisation.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('OrganisationService', () => { let service: OrganisationService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); service = TestBed.inject(OrganisationService); }); From 4502f8865acb483ee5e098a9b1afad3efa461563 Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 11:01:07 +0100 Subject: [PATCH 16/77] implement hidden view of team management icon if user is not part of leadership --- .../okr/controller/OrganisationController.java | 16 ++++++++++++---- .../OrganisationAuthorizationService.java | 5 +++++ .../application-top-bar.component.ts | 6 ++++++ .../app/shared/services/organisation.service.ts | 4 ++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java index 74972d8922..f8183b5243 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/OrganisationController.java @@ -30,13 +30,21 @@ public OrganisationController(OrganisationAuthorizationService organisationAutho } @Operation(summary = "Get all Organisations", description = "Get all Organisations") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Returned all Organisations", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class)) }), - @ApiResponse(responseCode = "401", description = "Not authorized to read all Organisations", content = @Content) }) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned all Organisations", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = OrganisationDto.class)) }), + @ApiResponse(responseCode = "401", description = "Not authorized to read organisations", content = @Content) }) @GetMapping public ResponseEntity> getOrganisations() { return ResponseEntity.status(HttpStatus.OK).body( organisationAuthorizationService.getEntities().stream().map(this.organisationMapper::toDto).toList()); } + + @Operation(summary = "Has access to read organisations", description = "Has access to read organisations") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Returned boolean if user has access", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = Boolean.class)) }) }) + @GetMapping("/access") + public ResponseEntity hasAccess() { + return ResponseEntity.ok(organisationAuthorizationService.hasAccess()); + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java index c098623fcc..d7c51ec7c2 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OrganisationAuthorizationService.java @@ -28,4 +28,9 @@ public List getEntities() { } return organisationBusinessService.getOrganisations(); } + + public boolean hasAccess() { + AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser(); + return authorizationUser.roles().contains(AuthorizationRole.WRITE_ALL); + } } diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index aceb7759c2..4a32b380d0 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { map, ReplaySubject } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { ConfigService } from '../config.service'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TeamManagementComponent } from '../shared/dialog/team-management/team-management.component'; +import { OrganisationService } from '../shared/services/organisation.service'; import { Router } from '@angular/router'; @Component({ @@ -14,17 +16,21 @@ import { Router } from '@angular/router'; }) export class ApplicationTopBarComponent implements OnInit { username: ReplaySubject = new ReplaySubject(); + teamManagementAccess$: Observable = new Observable(); menuIsOpen = false; private dialogRef!: MatDialogRef | undefined; + constructor( private oauthService: OAuthService, private configService: ConfigService, + private organisationService: OrganisationService, private dialog: MatDialog, private router: Router, ) {} ngOnInit(): void { + this.teamManagementAccess$ = this.organisationService.hasAccess(); this.configService.config$ .pipe( map((config) => { diff --git a/frontend/src/app/shared/services/organisation.service.ts b/frontend/src/app/shared/services/organisation.service.ts index 6e0c1659d8..eada5ca2a8 100644 --- a/frontend/src/app/shared/services/organisation.service.ts +++ b/frontend/src/app/shared/services/organisation.service.ts @@ -12,4 +12,8 @@ export class OrganisationService { getOrganisations(): Observable { return this.http.get('/api/v1/organisations'); } + + hasAccess(): Observable { + return this.http.get('/api/v1/organisations/access'); + } } From fc85fbdb80f5bf557ecd755dd9837ce913dc12d9 Mon Sep 17 00:00:00 2001 From: megli2 Date: Wed, 8 Nov 2023 12:08:08 +0100 Subject: [PATCH 17/77] style teammanagement to match other dialogs --- .../team-management.component.css | 0 .../team-management.component.html | 16 +++++++++++----- .../team-management.component.scss | 19 +++++++++++++++++++ .../team-management.component.ts | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) delete mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.css create mode 100644 frontend/src/app/shared/dialog/team-management/team-management.component.scss diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.css b/frontend/src/app/shared/dialog/team-management/team-management.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.html b/frontend/src/app/shared/dialog/team-management/team-management.component.html index 3852cd2350..385ab5020b 100644 --- a/frontend/src/app/shared/dialog/team-management/team-management.component.html +++ b/frontend/src/app/shared/dialog/team-management/team-management.component.html @@ -1,7 +1,7 @@
-
+