diff --git a/api-project-platform/pom.xml b/api-project-platform/pom.xml
index 1c15ede..b2b2b23 100644
--- a/api-project-platform/pom.xml
+++ b/api-project-platform/pom.xml
@@ -20,6 +20,16 @@
spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
org.springframework.boot
@@ -77,6 +87,10 @@
spring-boot-starter-test
test
+
+ org.springframework.security
+ spring-security-core
+
diff --git a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java
index c30f824..955fe90 100644
--- a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java
+++ b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImpl.java
@@ -1,5 +1,6 @@
package org.opendevstack.apiservice.projectplatform.facade.impl;
+import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.ProjectsInfoService;
@@ -10,6 +11,7 @@
import org.springframework.stereotype.Component;
@Component
+@Slf4j
public class ProjectsFacadeImpl implements ProjectsFacade {
private final ProjectsInfoService projectsInfoService;
@@ -30,4 +32,5 @@ public ProjectPlatforms getProjectPlatforms(String projectKey) throws ProjectPla
throw new ProjectPlatformsException("Failed to retrieve project platforms", e);
}
}
+
}
diff --git a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java
index de07a84..9b9b0af 100644
--- a/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java
+++ b/api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapper.java
@@ -31,9 +31,9 @@ public ProjectPlatforms toApiModel(Platforms externalPlatforms) {
ProjectPlatforms apiPlatforms = new ProjectPlatforms();
// Map sections
- if (externalPlatforms.getSections() != null) {
+ if (externalPlatforms.sections() != null) {
apiPlatforms.setSections(
- externalPlatforms.getSections().stream()
+ externalPlatforms.sections().stream()
.map(this::toApiSection)
.collect(Collectors.toList())
);
@@ -55,13 +55,13 @@ private Section toApiSection(PlatformSection externalSection) {
}
Section apiSection = new Section();
- apiSection.setSection(externalSection.getSection());
- apiSection.setTooltip(externalSection.getTooltip());
+ apiSection.setSection(externalSection.section());
+ apiSection.setTooltip(externalSection.tooltip());
// Map links
- if (externalSection.getLinks() != null) {
+ if (externalSection.links() != null) {
apiSection.setLinks(
- externalSection.getLinks().stream()
+ externalSection.links().stream()
.map(this::toApiLink)
.collect(Collectors.toList())
);
diff --git a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java
index 9949664..d4e7e2b 100644
--- a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java
+++ b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/facade/impl/ProjectsFacadeImplTest.java
@@ -1,127 +1,82 @@
package org.opendevstack.apiservice.projectplatform.facade.impl;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.ProjectsInfoServiceException;
+import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms;
import org.opendevstack.apiservice.externalservice.projectsinfoservice.service.ProjectsInfoService;
import org.opendevstack.apiservice.projectplatform.exception.ProjectPlatformsException;
import org.opendevstack.apiservice.projectplatform.mapper.ProjectPlatformsMapper;
-import org.opendevstack.apiservice.projectplatform.model.Link;
import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms;
-import org.opendevstack.apiservice.projectplatform.model.Section;
+import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-@ExtendWith(MockitoExtension.class)
class ProjectsFacadeImplTest {
- @Mock
private ProjectsInfoService projectsInfoService;
-
- @Mock
private ProjectPlatformsMapper mapper;
- private ProjectsFacadeImpl facade;
+ private ProjectsFacadeImpl sut;
@BeforeEach
- void setUp() {
- facade = new ProjectsFacadeImpl(projectsInfoService, mapper);
- }
-
- @Test
- void givenAnyProjectKey_whenGetProjectPlatforms_thenGetMockProjectPlatforms() throws ProjectsInfoServiceException, ProjectPlatformsException {
- // Arrange
- String projectKey = "DEVSTACK";
+ void setup() {
+ projectsInfoService = mock(ProjectsInfoService.class);
+ mapper = mock(ProjectPlatformsMapper.class);
- // Create external service response
- Platforms externalPlatforms = new Platforms();
+ sut = new ProjectsFacadeImpl(projectsInfoService, mapper);
- // Create expected API response
- ProjectPlatforms expectedPlatforms = createExpectedProjectPlatforms();
+ // Reset security context before each test
+ SecurityContextHolder.clearContext();
+ }
- // Mock behavior
- when(projectsInfoService.getProjectPlatforms(projectKey)).thenReturn(externalPlatforms);
- when(mapper.toApiModel(externalPlatforms)).thenReturn(expectedPlatforms);
-
- // Act
- ProjectPlatforms result = facade.getProjectPlatforms(projectKey);
-
- // Assert
- assertNotNull(result, "Result should not be null");
-
- List sections = result.getSections();
- assertNotNull(sections, "Sections should not be null");
- assertEquals(3, sections.size(), "There should be 3 sections");
-
- // Validate first section
- Section appPlatformSection = sections.get(0);
- assertEquals("Project Shortcuts - Application Platform", appPlatformSection.getSection());
- assertEquals(4, appPlatformSection.getLinks().size());
- assertTrue(appPlatformSection.getLinks().stream().anyMatch(link -> link.getLabel().equals("JIRA")));
- assertTrue(appPlatformSection.getLinks().stream().allMatch(link -> link.getUrl().equals("https://www.google.com")));
-
- // Validate second section
- Section dataPlatformSection = sections.get(1);
- assertEquals("Project Shortcuts - Data Platform", dataPlatformSection.getSection());
- assertEquals(2, dataPlatformSection.getLinks().size());
-
- // Validate third section
- Section servicesSection = sections.get(2);
- assertEquals("Services", servicesSection.getSection());
- assertEquals(3, servicesSection.getLinks().size());
-
- // Verify interactions
- verify(projectsInfoService, times(1)).getProjectPlatforms(projectKey);
- verify(mapper, times(1)).toApiModel(externalPlatforms);
+ @AfterEach
+ void cleanup() {
+ SecurityContextHolder.clearContext();
}
@Test
- void givenProjectsInfoServiceThrowsException_whenGetProjectPlatforms_thenRuntimeExceptionIsThrown() throws ProjectsInfoServiceException {
- // Arrange
- String projectKey = "DEVSTACK";
- when(projectsInfoService.getProjectPlatforms(projectKey))
- .thenThrow(new ProjectsInfoServiceException("Service error"));
+ void getProjectPlatforms_whenGetProjectPlatforms_thenReturnMappedApiModel() throws Exception {
+ //given
+ String projectKey = "PROJ";
- // Act & Assert
- ProjectPlatformsException exception = assertThrows(ProjectPlatformsException.class, () -> facade.getProjectPlatforms(projectKey));
+ List sections = List.of();
+ Platforms externalPlatforms = new Platforms(sections);
+ when(projectsInfoService.getProjectPlatforms(projectKey)).thenReturn(externalPlatforms);
- assertEquals("Failed to retrieve project platforms", exception.getMessage());
- verify(projectsInfoService, times(1)).getProjectPlatforms(projectKey);
- verify(mapper, never()).toApiModel(any());
- }
+ ProjectPlatforms mapped = new ProjectPlatforms();
+ when(mapper.toApiModel(externalPlatforms)).thenReturn(mapped);
- private ProjectPlatforms createExpectedProjectPlatforms() {
- ProjectPlatforms platforms = new ProjectPlatforms();
+ //when
+ ProjectPlatforms result = sut.getProjectPlatforms(projectKey);
- // Set sections
- Section appPlatformSection = new Section("Project Shortcuts - Application Platform", "tooltip", List.of(
- new Link("JIRA", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("Bitbucket", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("Confluence", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("Jenkins", "https://www.google.com", "tooltip", "type", "abbreviation", false)
- ));
+ //then
+ verify(projectsInfoService).getProjectPlatforms(projectKey);
+ verify(mapper).toApiModel(externalPlatforms);
- Section dataPlatformSection = new Section("Project Shortcuts - Data Platform", "tooltip", List.of(
- new Link("EKG", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("EDGC", "https://www.google.com", "tooltip", "type", "abbreviation", false)
- ));
+ assertThat(result).isSameAs(mapped);
+ }
- Section servicesSection = new Section("Services", "tooltip", List.of(
- new Link("Service Onboarding", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("Documentation", "https://www.google.com", "tooltip", "type", "abbreviation", false),
- new Link("Service Training", "https://www.google.com", "tooltip", "type", "abbreviation", false)
- ));
+ @Test
+ void getProjectPlatforms_whenInfoServiceThrowsException_thenWrapInProjectPlatformsException() throws Exception {
+ //given
+ String projectKey = "PROJ";
- platforms.setSections(List.of(appPlatformSection, dataPlatformSection, servicesSection));
+ when(projectsInfoService.getProjectPlatforms(projectKey))
+ .thenThrow(new ProjectsInfoServiceException("boom"));
- return platforms;
+ //when/then
+ assertThatThrownBy(() -> sut.getProjectPlatforms(projectKey))
+ .isInstanceOf(ProjectPlatformsException.class)
+ .hasMessageContaining("Failed to retrieve project platforms");
}
-}
+}
\ No newline at end of file
diff --git a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java
index 47b2f13..58f3a99 100644
--- a/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java
+++ b/api-project-platform/src/test/java/org/opendevstack/apiservice/projectplatform/mapper/ProjectPlatformsMapperTest.java
@@ -1,21 +1,18 @@
package org.opendevstack.apiservice.projectplatform.mapper;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionLink;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms;
-import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms;
-import org.opendevstack.apiservice.projectplatform.model.Section;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSection;
+import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionLink;
+import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platforms;
+import org.opendevstack.apiservice.projectplatform.model.ProjectPlatforms;
+import org.opendevstack.apiservice.projectplatform.model.Section;
-import java.net.URI;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -73,27 +70,7 @@ void testToApiModel_WithCompleteData_MapsAllFields() {
@Test
void testToApiModel_WithNullPlatformLinks_MapsOtherFields() {
// Given
- Platforms externalPlatforms =
- new Platforms();
- externalPlatforms.setSections(new ArrayList<>());
-
- // When
- ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
-
- // Then
- assertNotNull(result);
- assertNotNull(result.getSections());
- assertTrue(result.getSections().isEmpty());
- }
-
- @Test
- void testToApiModel_WithNullDisabledPlatforms_MapsOtherFields() {
- // Given
- Platforms externalPlatforms =
- new Platforms();
- Map platformLinks = new HashMap<>();
- platformLinks.put("github", URI.create("https://github.com"));
- externalPlatforms.setSections(new ArrayList<>());
+ Platforms externalPlatforms = new Platforms(new ArrayList<>());
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -107,11 +84,7 @@ void testToApiModel_WithNullDisabledPlatforms_MapsOtherFields() {
@Test
void testToApiModel_WithNullSections_MapsOtherFields() {
// Given
- Platforms externalPlatforms =
- new Platforms();
- Map platformLinks = new HashMap<>();
- platformLinks.put("github", URI.create("https://github.com"));
- externalPlatforms.setSections(null);
+ Platforms externalPlatforms = new Platforms(null);
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -121,34 +94,11 @@ void testToApiModel_WithNullSections_MapsOtherFields() {
assertTrue(result.getSections().isEmpty());
}
- @Test
- void testToApiModel_WithEmptyCollections_ReturnsEmptyCollections() {
- // Given
- Platforms externalPlatforms =
- new Platforms();
- externalPlatforms.setSections(new ArrayList<>());
-
- // When
- ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
-
- // Then
- assertNotNull(result);
- assertNotNull(result.getSections());
- assertTrue(result.getSections().isEmpty());
- }
-
@Test
void testToApiModel_WithSectionContainingNullLinks_HandlesGracefully() {
// Given
- Platforms externalPlatforms =
- new Platforms();
-
- PlatformSection section =
- new PlatformSection();
- section.setSection("Development");
- section.setLinks(null);
-
- externalPlatforms.setSections(List.of(section));
+ PlatformSection section = new PlatformSection("Development", null, null);
+ Platforms externalPlatforms = new Platforms(List.of(section));
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -164,15 +114,9 @@ void testToApiModel_WithSectionContainingNullLinks_HandlesGracefully() {
@Test
void testToApiModel_WithSectionContainingEmptyLinks_ReturnsEmptyLinks() {
// Given
- Platforms externalPlatforms =
- new Platforms();
+ PlatformSection section = new PlatformSection("Development", null, new ArrayList<>());
- PlatformSection section =
- new PlatformSection();
- section.setSection("Development");
- section.setLinks(new ArrayList<>());
-
- externalPlatforms.setSections(List.of(section));
+ Platforms externalPlatforms = new Platforms(List.of(section));
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -189,14 +133,11 @@ void testToApiModel_WithSectionContainingEmptyLinks_ReturnsEmptyLinks() {
@Test
void testToApiModel_WithSectionsContainingNullSection_HandlesGracefully() {
// Given
- Platforms externalPlatforms =
- new Platforms();
-
List sections =
new ArrayList<>();
sections.add(null);
- externalPlatforms.setSections(sections);
+ Platforms externalPlatforms = new Platforms(sections);
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -211,19 +152,13 @@ void testToApiModel_WithSectionsContainingNullSection_HandlesGracefully() {
@Test
void testToApiModel_WithLinksContainingNullLink_HandlesGracefully() {
// Given
- Platforms externalPlatforms =
- new Platforms();
+ List links = new ArrayList<>();
+ links.add(null);
- PlatformSection section =
- new PlatformSection();
- section.setSection("Development");
+ PlatformSection section = new PlatformSection("Development", null, links);
- List links =
- new ArrayList<>();
- links.add(null);
- section.setLinks(links);
+ Platforms externalPlatforms = new Platforms(List.of(section));
- externalPlatforms.setSections(List.of(section));
// When
ProjectPlatforms result = mapper.toApiModel(externalPlatforms);
@@ -259,17 +194,7 @@ void testToApiModel_WithMultipleSectionsAndLinks_MapsCorrectly() {
// Helper method to create a complete external ProjectPlatforms object
private Platforms createCompleteExternalProjectPlatforms() {
- Platforms externalPlatforms =
- new Platforms();
-
- // Setup sections
- List sections = new ArrayList<>();
-
// First section with 2 links
- PlatformSection section1 =
- new PlatformSection();
- section1.setSection("Development");
-
List links1 = new ArrayList<>();
PlatformSectionLink link1 =
@@ -284,14 +209,9 @@ private Platforms createCompleteExternalProjectPlatforms() {
link2.setUrl("https://jira.com/board");
links1.add(link2);
- section1.setLinks(links1);
- sections.add(section1);
+ PlatformSection section1 = new PlatformSection("Development", null, links1);
// Second section with 1 link
- PlatformSection section2 =
- new PlatformSection();
- section2.setSection("CI/CD");
-
List links2 = new ArrayList<>();
PlatformSectionLink link3 =
@@ -300,12 +220,14 @@ private Platforms createCompleteExternalProjectPlatforms() {
link3.setUrl("https://jenkins.com/job");
links2.add(link3);
- section2.setLinks(links2);
- sections.add(section2);
+ PlatformSection section2 =
+ new PlatformSection("CI/CD", null, links2);
- externalPlatforms.setSections(sections);
+ // Setup sections
+ List sections = new ArrayList<>(List.of(section1, section2));
- return externalPlatforms;
+ return new Platforms(sections);
}
+
}
diff --git a/application.yaml b/application.yaml
index 9ee9506..603ecc0 100644
--- a/application.yaml
+++ b/application.yaml
@@ -170,87 +170,3 @@ externalservices:
projects-info-service:
base-url: ${PROJECTS_INFO_SERVICE_BASE_URL:http://localhost:8081}
- ssl:
- verify-certificates: ${PROJECTS_INFO_SERVICE_SSL_VERIFY:true}
- trust-store-path: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_PATH:}
- trust-store-password: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_PASSWORD:}
- trust-store-type: ${PROJECTS_INFO_SERVICE_SSL_TRUSTSTORE_TYPE:JKS}
- azure:
- access-token: ${PROJECTS_INFO_SERVICE_AZURE_ACCESS_TOKEN:tbc}
- datahub:
- group-id: ${PROJECTS_INFO_SERVICE_AZURE_DATA_HUB_GROUP_ID:tbc}
- groups:
- page-size: ${PROJECTS_INFO_SERVICE_AZURE_GROUPS_PAGE_SIZE:100}
- testing-hub:
- default:
- projects: ${PROJECTS_INFO_SERVICE_TESTING_HUB_DEFAULT_PROJECTS:tbc}
- api:
- url: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_URL:tbc}
- token: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_TOKEN:tbc}
- page-size: ${PROJECTS_INFO_SERVICE_TESTING_HUB_API_PAGE_SIZE:100}
- custom:
- cache:
- specs:
- userGroups:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100}
- userGroups-fallback:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- userEmail:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100}
- userEmail-fallback:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- allEdpProjects:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100}
- allEdpProjects-fallback:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- projectsInfoCache:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100}
- projectsInfoCache-fallback:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- openshiftProjects:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_TTL_SECONDS:60}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_MAXIMUM_SIZE:100}
- openshiftProjects-fallback:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- dataHubGroups:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- testingHubGroups:
- ttl: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_TTL_SECONDS:120}
- maxSize: ${PROJECTS_INFO_SERVICE_CUSTOM_CACHE_FALLBACK_MAXIMUM_SIZE:100}
- mock:
- clusters: ${PROJECTS_INFO_SERVICE_MOCK_CLUSTERS:tbc}
- projects:
- default: ${PROJECTS_INFO_SERVICE_MOCK_DEFAULT_PROJECTS:tbc}
- users: ${PROJECTS_INFO_SERVICE_MOCK_USER_PROJECTS:tbc}
- openshift:
- api:
- clusters:
- test:
- url: ${PROJECTS_INFO_SERVICE_OPENSHIFT_TEST_URL:tbc}
- token: ${PROJECTS_INFO_SERVICE_OPENSHIFT_TEST_TOKEN:tbc}
- dev:
- url: ${PROJECTS_INFO_SERVICE_OPENSHIFT_DEV_URL:tbc}
- token: ${PROJECTS_INFO_SERVICE_OPENSHIFT_DEV_TOKEN:tbc}
- project:
- url: /apis/project.openshift.io/v1/projects
- platforms:
- bearer-token: ${PROJECTS_INFO_SERVICE_PLATFORMS_BEARER_TOKEN:tbc}
- base-path: ${PROJECTS_INFO_SERVICE_PLATFORMS_BASE_PATH:tbc}
- clusters:
- test: ${PROJECTS_INFO_SERVICE_PLATFORMS_TEST_CLUSTER:tbc}
- dev: ${PROJECTS_INFO_SERVICE_PLATFORMS_DEV_CLUSTER:tbc}
- project:
- filter:
- project-roles-group-prefix:
- # Properties to be used as lists cannot have leading or trailing blanks.
- project-roles-group-suffixes: ROLE-A,ROLE-B
diff --git a/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml b/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml
new file mode 100644
index 0000000..251ab8f
--- /dev/null
+++ b/external-service-projects-info-service/openapi/openapi-projects-info-service-v1.0.0.yaml
@@ -0,0 +1,264 @@
+openapi: 3.0.3
+info:
+ title: Projects info service REST API
+ version: '1.0.0'
+ description: >
+ The Projects Info Service API allows clients to collect project information for a predefined user. This user will be
+ Managed thanks to SSO token.
+
+ **NOTES**:
+ - The OpenAPI specification file is also used to [generate](https://openapi-generator.tech/) REST client(s) and a server REST API.
+ - Clients and servers generated from the same OpenAPI specification version are guaranteed to be **compatible**.
+ contact:
+ name: OpenDevStack
+ url: https://www.opendevstack.org/
+servers:
+ - url: http://{baseurl}/v1
+ variables:
+ baseurl:
+ default: localhost:8080
+ description: Default address for a Projects Info Service's backend REST API instance.
+security:
+ - bearerAuth: []
+tags:
+ - name: AzureGroups
+ description: Get azure groups operations.
+ - name: Projects
+ description: Get projects operations.
+paths:
+ /azure/groups:
+ get:
+ tags:
+ - AzureGroups
+ summary: Get all azure groups associated to the user.
+ description: >
+ This endpoint receives an azure token, and returns all the groups associated to the user.
+ operationId: getAzureGroups
+ parameters:
+ - name: token
+ in: header
+ required: true
+ schema:
+ type: string
+ description: Azure token used to get the groups.
+ responses:
+ "200":
+ description: List of azure groups associated to the user.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ "401":
+ description: Invalid client token on the request.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "403":
+ description: Insufficient permissions for the client to access the resource.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "500":
+ description: Server error.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ /projects:
+ get:
+ tags:
+ - Projects
+ summary: Get all the projects a user get access to.
+ description: >
+ Get all the projects a user get access to. For that, first of all it will get all the azure groups associated to the user,
+ and then it will get all the projects associated to those groups.
+ operationId: getProjects
+ parameters:
+ - name: token
+ in: header
+ required: true
+ schema:
+ type: string
+ description: Azure token used to get the groups.
+ responses:
+ "200":
+ description: List of projects the user has access to.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ "401":
+ description: Invalid client token on the request.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "403":
+ description: Insufficient permissions for the client to access the resource.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "500":
+ description: Server error.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ /projects/{projectKey}/clusters:
+ get:
+ tags:
+ - Projects
+ summary: Get all project info and cluster for a given project key.
+ description: >
+ Get all project info and cluster for a given project key.
+ operationId: getProjectClusters
+ parameters:
+ - name: token
+ in: header
+ required: true
+ schema:
+ type: string
+ description: Azure token used to get the groups.
+ - name: projectKey
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Project id to get the info and clusters.
+ responses:
+ "200":
+ description: Project info.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectInfo'
+
+ "401":
+ description: Invalid client token on the request.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "403":
+ description: Insufficient permissions for the client to access the resource.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "500":
+ description: Server error.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+
+ /projects/{projectKey}/platforms:
+ get:
+ tags:
+ - Projects
+ summary: Get platform information for a given project key.
+ description: Returns disabled platforms and categorized platform links for the specified project.
+ operationId: getProjectPlatforms
+ parameters:
+ - name: projectKey
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Project key to retrieve platform information.
+ responses:
+ '200':
+ description: Platform information for the project.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectPlatforms'
+ "401":
+ description: Invalid client token on the request.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+ "403":
+ description: Insufficient permissions for the client to access the resource.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RestErrorMessage'
+
+components:
+ securitySchemes:
+ bearerAuth:
+ type: http
+ description: >
+ Authorization via bearer token.
+ scheme: bearer
+ bearerFormat: JWT
+ schemas:
+ RestErrorMessage:
+ properties:
+ message:
+ type: string
+ required:
+ - message
+ ProjectInfo:
+ type: object
+ properties:
+ projectKey:
+ type: string
+ description: Project KEY.
+ clusters:
+ type: array
+ items:
+ type: string
+ description: List of clusters associated to the project.
+ required:
+ - projectKey
+ - projectName
+ - clusters
+ Link:
+ type: object
+ properties:
+ label:
+ type: string
+ url:
+ type: string
+ tooltip:
+ type: string
+ type:
+ type: string
+ abbreviation:
+ type: string
+ disabled:
+ type: boolean
+ required:
+ - label
+ - url
+ Section:
+ type: object
+ properties:
+ section:
+ type: string
+ tooltip:
+ type: string
+ links:
+ type: array
+ items:
+ $ref: '#/components/schemas/Link'
+ required:
+ - section
+ - links
+ ProjectPlatforms:
+ type: object
+ properties:
+ sections:
+ type: array
+ items:
+ $ref: '#/components/schemas/Section'
\ No newline at end of file
diff --git a/external-service-projects-info-service/pom.xml b/external-service-projects-info-service/pom.xml
index 9eafead..cd427a6 100644
--- a/external-service-projects-info-service/pom.xml
+++ b/external-service-projects-info-service/pom.xml
@@ -78,6 +78,22 @@
3.18.0
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+ provided
+
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
org.springframework.boot
@@ -102,6 +118,69 @@
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+ generate-api-project-users
+
+ generate
+
+
+ java
+
+ resttemplate
+ ${project.basedir}/openapi/openapi-projects-info-service-v1.0.0.yaml
+ org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.api
+ org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client.model
+ org.opendevstack.apiservice.externalservice.projects_info_service.v1_0_0.client
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ default=defaultValue
+
+ false
+ true
+ true
+ true
+ true
+ true
+
+
+ java8
+ jackson
+ true
+
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.basedir}/target/generated-sources/openapi/src/main/java
+
+
+
+
+
+
org.springframework.boot
spring-boot-maven-plugin
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java
deleted file mode 100644
index a18704f..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallback.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations;
-
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.CacheConfiguration;
-
-import java.lang.annotation.*;
-
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface CacheableWithFallback {
- String primary();
- String fallback();
- String defaultValue() default ""; // SpEL or literal string
- String cacheManager() default CacheConfiguration.CUSTOM_CACHE_MANAGER_NAME;
-}
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java
deleted file mode 100644
index 13e0638..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/annotations/CacheableWithFallbackAspect.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations;
-
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
-import org.springframework.cache.interceptor.SimpleKeyGenerator;
-import org.springframework.core.annotation.Order;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
-import org.springframework.stereotype.Component;
-
-import java.lang.reflect.Method;
-
-@AllArgsConstructor
-@Slf4j
-@Aspect
-@Component
-@Order(1)
-public class CacheableWithFallbackAspect {
-
- private static final String EMPTY_STRING = "";
- private final CacheManager cacheManager;
-
- @Around("@annotation(cacheableWithFallback)")
- public Object cacheWithFallback(ProceedingJoinPoint pjp, CacheableWithFallback cacheableWithFallback) {
- log.debug("CacheWithFallback. pjp: {}, annotation: {}", pjp, cacheableWithFallback);
-
- String primary = cacheableWithFallback.primary();
- String fallback = cacheableWithFallback.fallback();
- Object defaultValue = getDefaultValue(pjp, cacheableWithFallback);
-
- Object key = generateKey(pjp);
- Cache primaryCache = cacheManager.getCache(primary);
- Cache fallbackCache = cacheManager.getCache(fallback);
-
- Object result = null;
-
- if (primaryCache != null) {
- result = extractValueFromPrimaryCache(primaryCache, key);
- }
-
- if (result == null) {
- Object pjpResult = extractValueFromRealMethodCall(pjp);
-
- if (pjpResult != null) {
- result = pjpResult;
-
- updateCaches(primaryCache, fallbackCache, (String) key, result);
- } else if (fallbackCache != null) {
- result = extractValueFromFallbackCache(fallbackCache, key, defaultValue);
- } else {
- result = defaultValue;
-
- log.debug("No fallback cache configured. Returning default value: {}", defaultValue);
- }
- }
-
- log.debug("CacheableWithFallback result: {}", result);
- return result;
- }
-
- protected Object getDefaultValue(ProceedingJoinPoint pjp, CacheableWithFallback cacheableWithFallback) {
- String defaultValueStr = cacheableWithFallback.defaultValue();
- Method method = ((MethodSignature) pjp.getSignature()).getMethod();
- Class> returnType = method.getReturnType();
-
- if (defaultValueStr != null && defaultValueStr.equals(EMPTY_STRING)) {
- return EMPTY_STRING;
- } else {
- return resolveDefaultValue(defaultValueStr, returnType);
- }
- }
-
-
- protected Object resolveDefaultValue(String defaultValue, Class> returnType) {
- if (defaultValue == null || defaultValue.isEmpty()) return null;
-
- ExpressionParser parser = new SpelExpressionParser();
- StandardEvaluationContext context = new StandardEvaluationContext();
-
- Object result = parser.parseExpression(defaultValue).getValue(context);
-
- if (result != null && !returnType.isAssignableFrom(result.getClass())) {
- throw new IllegalArgumentException("Default value type mismatch: expected " +
- returnType.getName() + ", but got " + result.getClass().getName());
- }
-
- return result;
- }
-
- protected Object generateKey(ProceedingJoinPoint pjp) {
- log.debug("Generating key for method: {}", pjp.getSignature().getName());
-
- Object generatedKey;
- Object[] args = pjp.getArgs();
-
- if (args == null || args.length == 0) {
- generatedKey = pjp.getSignature().getName();
- } else {
- generatedKey = SimpleKeyGenerator.generateKey(pjp.getArgs());
- }
-
- log.debug("Generated key: {}", generatedKey);
-
- return generatedKey;
- }
-
- private Object extractValueFromPrimaryCache(Cache primaryCache, Object cacheKey) {
- Object result = null;
-
- Cache.ValueWrapper primaryCachedValue = primaryCache.get(cacheKey);
- if (primaryCachedValue != null) {
- result = primaryCachedValue.get();
-
- log.debug("Returning value from primary cache: {}, key: {}", primaryCache.getName(), cacheKey);
- }
-
- return result;
- }
-
- private Object extractValueFromFallbackCache(Cache fallbackCache, Object cacheKey, Object defaultValue) {
- log.debug("There were an issue getting the real value. Trying to get value from fallback cache: {}, key: {}", fallbackCache, cacheKey);
-
- Object result;
-
- Cache.ValueWrapper fallbackCachedValue = fallbackCache.get(cacheKey);
- if (fallbackCachedValue != null) {
- result = fallbackCachedValue.get();
-
- log.debug("Returning value from fallback cache: {}, key: {}", fallbackCache, cacheKey);
- } else {
- result = defaultValue;
-
- log.debug("No value in fallback cache. Returning default value: {}", defaultValue);
- }
-
- return result;
- }
-
- private Object extractValueFromRealMethodCall(ProceedingJoinPoint pjp) {
- Object pjpResult = null;
-
- try {
- log.debug("Calling real method: {}", pjp.getSignature().getName());
-
- pjpResult = pjp.proceed(); // call the actual method
- } catch (Throwable throwable) {
- log.error("There were an error calling real method: {}", pjp.getSignature().getName(), throwable);
- }
-
- return pjpResult;
- }
-
- private void updateCaches(Cache primaryCache, Cache fallbackCache, String key, Object result) {
- log.debug("Real method call generated a result. Updating caches: {}, {}, key: {}", primaryCache, fallbackCache, key);
-
- if (primaryCache != null) {
- primaryCache.put(key, result);
- }
-
- if (fallbackCache != null) {
- fallbackCache.put(key, result);
- }
-
- log.debug("Caches updated. Primary: {}, Fallback: {}, key: {}", primaryCache, fallbackCache, key);
- }
-}
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java
deleted file mode 100644
index fe1bde3..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/AzureGraphClient.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.client;
-
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.annotations.CacheableWithFallback;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidContentProcessException;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.InvalidTokenException;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.exception.UnableToReachAzureException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.http.*;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.HttpClientErrorException;
-import org.springframework.web.client.RestTemplate;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-@Slf4j
-@Service
-public class AzureGraphClient {
-
- private static final String USER_INFO_URL = "https://graph.microsoft.com/v1.0/me";
- private static final String MEMBER_OF_URL = "https://graph.microsoft.com/v1.0/me/memberOf";
- public static final String ERROR_WHILE_GETTING_USER_GROUPS = "Error while getting user groups";
- public static final String ERROR_WHILE_PROCESSING_SERVER_RESPONSE = "Error while processing server response";
- public static final String ERROR_WHILE_GETTING_APPLICATION_GROUPS = "Error while getting application groups";
-
- private final RestTemplate restTemplate;
- private final ObjectMapper mapper;
-
- @Value("${externalservices.projects-info-service.azure.groups.page-size}")
- private Integer pageSize;
-
- @Value("${externalservices.projects-info-service.azure.access-token}")
- private String azureAccessToken;
-
- @Value("${externalservices.projects-info-service.azure.datahub.group-id}")
- private String dataHubGroupId;
-
- AzureGraphClient(RestTemplate restTemplate) {
- this.restTemplate = restTemplate;
- this.mapper = new ObjectMapper();
- }
-
- public Set getUserGroups(String userAccessToken) {
- Set groupIds = new HashSet<>();
- String url = MEMBER_OF_URL + "?$top=" + pageSize; // e.g., "https://graph.microsoft.com/v1.0/me/memberOf?$top=100"
-
- HttpHeaders headers = new HttpHeaders();
- headers.setBearerAuth(userAccessToken);
- headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
- HttpEntity entity = new HttpEntity<>(headers);
-
- try {
- while (url != null) {
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
- groupIds.addAll(processAzureResponse(response));
-
- // Parse the nextLink if present
- JsonNode root = new ObjectMapper().readTree(response.getBody());
- JsonNode nextLinkNode = root.get("@odata.nextLink");
-
- if (nextLinkNode != null) {
- log.debug("Next link found: {}", nextLinkNode.asText());
-
- url = nextLinkNode != null ? nextLinkNode.asText() : null;
- } else {
- url = null; // No more pages
- }
- }
- } catch (HttpClientErrorException | IOException e) {
- log.error(ERROR_WHILE_GETTING_USER_GROUPS, e);
- throw new InvalidTokenException(ERROR_WHILE_GETTING_USER_GROUPS, e);
- }
-
- return groupIds;
- }
-
- // TODO: Can not use short circuit at the moment, we will do it when integration is completed
- @Cacheable("dataHubGroups")
- public Set getDataHubGroups() {
- return getApplicationGroups(azureAccessToken, dataHubGroupId);
- }
-
- // TODO: Can not use short circuit at the moment, we will do it when integration is completed
- @Cacheable("testingHubGroups")
- public Set getTestingHubGroups() {
- return getApplicationGroups(azureAccessToken, dataHubGroupId);
- }
-
- protected Set getApplicationGroups(String accessToken, String appObjectId) {
- Set groupNames = new HashSet<>();
- String url = "https://graph.microsoft.com/v1.0/servicePrincipals/" + appObjectId + "/appRoleAssignedTo?$top=" + pageSize;
-
- HttpHeaders headers = new HttpHeaders();
- headers.setBearerAuth(accessToken);
- headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
- HttpEntity entity = new HttpEntity<>(headers);
-
- try {
- while (url != null) {
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
- groupNames.addAll(processAzureResponse(response));
-
- JsonNode root = mapper.readTree(response.getBody());
- JsonNode nextLinkNode = root.get("@odata.nextLink");
- url = nextLinkNode != null ? nextLinkNode.asText() : null;
- }
- } catch (HttpClientErrorException e) {
- log.error(ERROR_WHILE_GETTING_APPLICATION_GROUPS, e);
-
- if(e.getStatusCode() == HttpStatus.UNAUTHORIZED || e.getStatusCode() == HttpStatus.FORBIDDEN) {
- throw new UnableToReachAzureException(ERROR_WHILE_GETTING_APPLICATION_GROUPS, e);
- } else {
- log.trace("There were an error when trying to get groups. We continue the process and consider platform as disabled, " +
- "in order to not block application usage", e);
- }
- } catch (IOException e) {
- log.error(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e);
- }
-
- return groupNames;
- }
-
- @CacheableWithFallback(primary = "userEmail", fallback = "userEmail-fallback")
- public String getUserEmail(String accessToken) {
- HttpHeaders headers = new HttpHeaders();
- headers.setBearerAuth(accessToken);
- headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
-
- HttpEntity entity = new HttpEntity<>(headers);
-
- try {
- ResponseEntity response = restTemplate.exchange(USER_INFO_URL, HttpMethod.GET, entity, String.class);
- JsonNode root = mapper.readTree(response.getBody());
-
- // Try to get mail or userPrincipalName
- if (root.has("mail") && !root.get("mail").isNull()) {
- return root.get("mail").asText();
- } else if (root.has("userPrincipalName")) {
- return root.get("userPrincipalName").asText();
- } else {
- throw new InvalidContentProcessException("Email not found in user profile");
- }
-
- } catch (HttpClientErrorException e) {
- log.error("Error while getting user email", e);
- throw new InvalidTokenException("Error while getting user email", e);
- } catch (Exception e) {
- log.error("Error while processing user email response", e);
- throw new InvalidContentProcessException("Error while processing user email response", e);
- }
- }
-
-
- private Set processAzureResponse(ResponseEntity response) {
- log.trace("Processing Azure response: {}", response.getBody());
-
- Set groups = new HashSet<>();
-
- try {
- JsonNode root = mapper.readTree(response.getBody());
- JsonNode values = root.path("value");
-
- for (JsonNode group : values) {
- if (group.has("displayName")) {
- groups.add(group.get("displayName").asText());
- }
- }
- } catch (Exception e) {
- log.error(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e);
-
- throw new InvalidContentProcessException(ERROR_WHILE_PROCESSING_SERVER_RESPONSE, e);
- }
-
- return groups;
- }
-}
-
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java
deleted file mode 100644
index ac1ab89..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/PlatformsYmlClient.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.client;
-
-import org.apache.commons.lang3.tuple.Pair;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.config.PlatformsConfiguration;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.Platform;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformSectionService;
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.PlatformsYml;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import lombok.AllArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.List;
-
-@Slf4j
-@AllArgsConstructor
-@Service
-public class PlatformsYmlClient {
-
- private final RestTemplate restTemplate;
- private final PlatformsConfiguration platformsConfiguration;
- private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
-
- @SneakyThrows
- public List fetchSectionsFromYaml(String url) {
-
- HttpHeaders headers = new HttpHeaders();
- headers.setBearerAuth(platformsConfiguration.getBearerToken()); // Adds "Authorization: Bearer "
- HttpEntity entity = new HttpEntity<>(headers);
-
- log.debug("Fetching sections from YAML. url={}", url);
-
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
- String yamlContent = response.getBody();
-
- PlatformsYml root = yamlMapper.readValue(yamlContent, PlatformsYml.class);
- return root.getSections();
- }
-
- @SneakyThrows
- public Pair> fetchPlatformsFromYaml(String url) {
-
- HttpHeaders headers = new HttpHeaders();
- headers.setBearerAuth(platformsConfiguration.getBearerToken()); // Adds "Authorization: Bearer "
- HttpEntity entity = new HttpEntity<>(headers);
-
- log.debug("Fetching sections from YAML. url={}", url);
-
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
- String yamlContent = response.getBody();
-
- PlatformsYml root = yamlMapper.readValue(yamlContent, PlatformsYml.class);
- return Pair.of(root.getTitle(), root.getPlatforms());
- }
-}
-
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java
deleted file mode 100644
index 96012fa..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/client/TestingHubClient.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.client;
-
-import org.opendevstack.apiservice.externalservice.projectsinfoservice.model.TestingHubProject;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.*;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Service
-public class TestingHubClient {
-
- @Value("${externalservices.projects-info-service.testing-hub.api.url}")
- private String url;
-
- @Value("${externalservices.projects-info-service.testing-hub.api.token}")
- private String token;
-
- @Value("${externalservices.projects-info-service.testing-hub.api.page-size}")
- private String pageSize;
-
- @Value("#{'${externalservices.projects-info-service.testing-hub.default.projects}'.split(',')}")
- public List defaultProjects;
-
- private final RestTemplate restTemplate;
- private final ObjectMapper jacksonMapper;
-
- public TestingHubClient(RestTemplate restTemplate, ObjectMapper jacksonMapper) {
- this.restTemplate = restTemplate;
- this.jacksonMapper = jacksonMapper;
- }
-
- public Set getDefaultProjects() {
- return defaultProjects.stream()
- .map(String::trim)
- .map(this::parseKeyAndId)
- .map(keyAndId -> new TestingHubProject(keyAndId.getRight(), keyAndId.getLeft()))
- .collect(Collectors.toSet());
- }
-
- // TODO: This method will be used when TestingHub notifies the changes on their API.
- public Set getAllProjects() {
- HttpHeaders headers = new HttpHeaders();
- headers.set("accept", "application/json");
- headers.set("X-Id-Token", "Bearer " + token);
-
- HttpEntity entity = new HttpEntity<>(headers);
-
- // Let's discuss later about pagination handling
- var requestUrl = url + "?pageNumber=0&itemsPerPage=" + pageSize;
-
- ResponseEntity response = restTemplate.exchange(
- requestUrl,
- HttpMethod.GET,
- entity,
- String.class
- );
-
- if (response.getStatusCode() == HttpStatus.OK) {
- String responseBody = response.getBody();
-
- try {
- return jacksonMapper.readValue(responseBody, new TypeReference<>() {});
- } catch (JsonProcessingException e) {
- log.error("Failed to deserialize response", e);
-
- return Collections.emptySet();
- }
- } else {
- log.error("Failed to fetch projects from Testing Hub. Status code: {}", response.getStatusCode());
-
- return Collections.emptySet();
- }
- }
-
- private Pair parseKeyAndId(String keyAndId) {
- var parts = keyAndId.split(":");
-
- if (parts.length != 2) {
- throw new IllegalArgumentException("Invalid project key and id format: " + keyAndId +
- ". Expected format: :");
- }
-
- return new ImmutablePair<>(parts[0], parts[1]);
- }
-}
diff --git a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java b/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java
deleted file mode 100644
index e6297a6..0000000
--- a/external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/config/CacheConfiguration.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.opendevstack.apiservice.externalservice.projectsinfoservice.config;
-
-import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.cache.RemovalListener;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
-import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.cache.caffeine.CaffeineCache;
-import org.springframework.cache.support.SimpleCacheManager;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-@Configuration
-@EnableCaching
-@AllArgsConstructor
-@Slf4j
-public class CacheConfiguration {
-
- public static final String CUSTOM_CACHE_MANAGER_NAME = "customCacheManager";
-
- private final CacheSpecPropertiesConfiguration cacheSpecProperties;
-
- @Bean(CUSTOM_CACHE_MANAGER_NAME)
- public CacheManager customCacheManager() {
- SimpleCacheManager manager = new SimpleCacheManager();
-
- List caches = cacheSpecProperties.getSpecs().entrySet().stream()
- .map(entry -> createCache(entry.getKey(), entry.getValue()))
- .collect(Collectors.toList());
-
- manager.setCaches(caches);
- return manager;
- }
-
- // Update this method in case we need to support different cache types
- private Cache createCache(String name, CacheSpecPropertiesConfiguration.CacheSpec spec) {
- RemovalListener