Ajay Yadav commited on
Commit
d6afd6c
·
1 Parent(s): 3fc05e6

Initial deployment of da-admin-service-dev

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +27 -0
  2. README.md +34 -6
  3. build.gradle.kts +70 -0
  4. src/main/docker/Dockerfile +23 -0
  5. src/main/docker/Dockerfile.alpine-jlink +43 -0
  6. src/main/docker/Dockerfile.layered +34 -0
  7. src/main/docker/Dockerfile.native +20 -0
  8. src/main/java/com/dalab/adminservice/DaAdminServiceApplication.java +15 -0
  9. src/main/java/com/dalab/adminservice/client/IAutoarchivalTaskApiClient.java +22 -0
  10. src/main/java/com/dalab/adminservice/client/IAutocomplianceJobApiClient.java +19 -0
  11. src/main/java/com/dalab/adminservice/client/IAutodeleteTaskApiClient.java +22 -0
  12. src/main/java/com/dalab/adminservice/client/IAutolabelJobApiClient.java +22 -0
  13. src/main/java/com/dalab/adminservice/client/IDiscoveryJobApiClient.java +23 -0
  14. src/main/java/com/dalab/adminservice/config/KeycloakAdminClientConfig.java +58 -0
  15. src/main/java/com/dalab/adminservice/config/SpringSecurityAuditorAware.java +1 -0
  16. src/main/java/com/dalab/adminservice/controller/CloudConnectionController.java +97 -0
  17. src/main/java/com/dalab/adminservice/controller/JobStatusController.java +35 -0
  18. src/main/java/com/dalab/adminservice/controller/RoleController.java +33 -0
  19. src/main/java/com/dalab/adminservice/controller/ServiceConfigController.java +76 -0
  20. src/main/java/com/dalab/adminservice/controller/UserController.java +126 -0
  21. src/main/java/com/dalab/adminservice/dto/AggregatedJobStatusDTO.java +22 -0
  22. src/main/java/com/dalab/adminservice/dto/CloudConnectionDTO.java +53 -0
  23. src/main/java/com/dalab/adminservice/dto/CloudConnectionTestResultDTO.java +24 -0
  24. src/main/java/com/dalab/adminservice/dto/JobStatusDTO.java +25 -0
  25. src/main/java/com/dalab/adminservice/dto/RoleDTO.java +21 -0
  26. src/main/java/com/dalab/adminservice/dto/ServiceConfigDTO.java +29 -0
  27. src/main/java/com/dalab/adminservice/dto/UserDTO.java +38 -0
  28. src/main/java/com/dalab/adminservice/exception/ConflictException.java +15 -0
  29. src/main/java/com/dalab/adminservice/exception/KeycloakAdminException.java +8 -0
  30. src/main/java/com/dalab/adminservice/exception/NotFoundException.java +11 -0
  31. src/main/java/com/dalab/adminservice/mapper/CloudConnectionMapper.java +53 -0
  32. src/main/java/com/dalab/adminservice/mapper/RoleMapper.java +19 -0
  33. src/main/java/com/dalab/adminservice/mapper/ServiceConfigMapper.java +29 -0
  34. src/main/java/com/dalab/adminservice/mapper/UserMapper.java +41 -0
  35. src/main/java/com/dalab/adminservice/model/AbstractAuditableEntity.java +41 -0
  36. src/main/java/com/dalab/adminservice/model/CloudConnectionEntity.java +52 -0
  37. src/main/java/com/dalab/adminservice/model/ServiceConfigEntity.java +42 -0
  38. src/main/java/com/dalab/adminservice/model/enums/CloudProviderType.java +11 -0
  39. src/main/java/com/dalab/adminservice/repository/CloudConnectionRepository.java +12 -0
  40. src/main/java/com/dalab/adminservice/repository/ServiceConfigRepository.java +23 -0
  41. src/main/java/com/dalab/adminservice/service/ICloudConnectionService.java +18 -0
  42. src/main/java/com/dalab/adminservice/service/IEncryptionService.java +22 -0
  43. src/main/java/com/dalab/adminservice/service/IJobStatusService.java +16 -0
  44. src/main/java/com/dalab/adminservice/service/IRoleService.java +18 -0
  45. src/main/java/com/dalab/adminservice/service/IServiceConfigService.java +14 -0
  46. src/main/java/com/dalab/adminservice/service/IUserService.java +19 -0
  47. src/main/java/com/dalab/adminservice/service/impl/BasicEncryptionServiceImpl.java +59 -0
  48. src/main/java/com/dalab/adminservice/service/impl/CloudConnectionServiceImpl.java +166 -0
  49. src/main/java/com/dalab/adminservice/service/impl/JobStatusServiceImpl.java +108 -0
  50. src/main/java/com/dalab/adminservice/service/impl/RoleServiceImpl.java +44 -0
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM openjdk:21-jdk-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required packages
6
+ RUN apt-get update && apt-get install -y \
7
+ curl \
8
+ wget \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy application files
12
+ COPY . .
13
+
14
+ # Build application (if build.gradle.kts exists)
15
+ RUN if [ -f "build.gradle.kts" ]; then \
16
+ ./gradlew build -x test; \
17
+ fi
18
+
19
+ # Expose port
20
+ EXPOSE 8080
21
+
22
+ # Health check
23
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
24
+ CMD curl -f http://localhost:8080/actuator/health || exit 1
25
+
26
+ # Run application
27
+ CMD ["java", "-jar", "build/libs/da-admin-service.jar"]
README.md CHANGED
@@ -1,10 +1,38 @@
1
  ---
2
- title: Da Admin Service Dev
3
- emoji: 🚀
4
- colorFrom: indigo
5
- colorTo: red
6
  sdk: docker
7
- pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: da-admin-service (dev)
3
+ emoji: 🔧
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ app_port: 8080
8
  ---
9
 
10
+ # da-admin-service - dev Environment
11
+
12
+ This is the da-admin-service microservice deployed in the dev environment.
13
+
14
+ ## Features
15
+
16
+ - RESTful API endpoints
17
+ - Health monitoring via Actuator
18
+ - JWT authentication integration
19
+ - PostgreSQL database connectivity
20
+
21
+ ## API Documentation
22
+
23
+ Once deployed, API documentation will be available at:
24
+ - Swagger UI: https://huggingface.co/spaces/dalabsai/da-admin-service-dev/swagger-ui.html
25
+ - Health Check: https://huggingface.co/spaces/dalabsai/da-admin-service-dev/actuator/health
26
+
27
+ ## Environment
28
+
29
+ - **Environment**: dev
30
+ - **Port**: 8080
31
+ - **Java Version**: 21
32
+ - **Framework**: Spring Boot
33
+
34
+ ## Deployment
35
+
36
+ This service is automatically deployed via the DALab CI/CD pipeline.
37
+
38
+ Last updated: 2025-06-16 23:39:48
build.gradle.kts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ plugins {
2
+ java
3
+ id("org.springframework.boot") version "3.2.5"
4
+ id("io.spring.dependency-management") version "1.1.4"
5
+ }
6
+
7
+ group = "com.dalab"
8
+ version = "0.0.1-SNAPSHOT"
9
+
10
+ java {
11
+ sourceCompatibility = JavaVersion.VERSION_21
12
+ }
13
+
14
+ configurations {
15
+ compileOnly {
16
+ extendsFrom(configurations.annotationProcessor.get())
17
+ }
18
+ }
19
+
20
+ repositories {
21
+ mavenCentral()
22
+ }
23
+
24
+ dependencies {
25
+ // da-protos common entities and utilities
26
+ implementation(project(":da-protos"))
27
+
28
+ implementation("org.springframework.boot:spring-boot-starter-actuator")
29
+ implementation("org.springframework.boot:spring-boot-starter-data-jpa")
30
+ implementation("org.springframework.boot:spring-boot-starter-security")
31
+ implementation("org.springframework.boot:spring-boot-starter-validation")
32
+ implementation("org.springframework.boot:spring-boot-starter-web")
33
+ implementation("org.springframework.kafka:spring-kafka")
34
+ implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1") // For Feign clients
35
+ implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0") // OpenAPI
36
+
37
+ // MapStruct for DTO mapping
38
+ implementation("org.mapstruct:mapstruct:1.5.5.Final")
39
+ annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
40
+
41
+ // Lombok for reduced boilerplate
42
+ compileOnly("org.projectlombok:lombok")
43
+ annotationProcessor("org.projectlombok:lombok")
44
+ annotationProcessor("org.projectlombok:lombok-mapstruct-binding:0.2.0") // If using Lombok with MapStruct
45
+
46
+ // Hypersistence Utils for JSONB and other advanced types
47
+ implementation("io.hypersistence:hypersistence-utils-hibernate-62:3.7.0") // For Hibernate 6.2.x used by Spring Boot 3.x
48
+
49
+ runtimeOnly("org.postgresql:postgresql")
50
+ testImplementation("org.springframework.boot:spring-boot-starter-test")
51
+ testImplementation("org.springframework.kafka:spring-kafka-test")
52
+ testImplementation("org.springframework.security:spring-security-test")
53
+
54
+ // Keycloak Admin Client (if direct Keycloak interaction is needed for users/roles)
55
+ implementation("org.keycloak:keycloak-admin-client:24.0.4")
56
+ }
57
+
58
+ dependencyManagement {
59
+ imports {
60
+ mavenBom("org.springframework.cloud:spring-cloud-dependencies:2023.0.1")
61
+ }
62
+ }
63
+
64
+ tasks.withType<Test> {
65
+ useJUnitPlatform()
66
+ }
67
+
68
+ tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
69
+ archiveFileName.set("${project.name}.jar")
70
+ }
src/main/docker/Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-lean container using Google Distroless
2
+ # Expected final size: ~120-180MB (minimal base + JRE + JAR only)
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Copy JAR file
10
+ COPY build/libs/da-admin-service.jar app.jar
11
+
12
+ # Expose standard Spring Boot port
13
+ EXPOSE 8080
14
+
15
+ # Run application (distroless has no shell, so use exec form)
16
+ ENTRYPOINT ["java", \
17
+ "-XX:+UseContainerSupport", \
18
+ "-XX:MaxRAMPercentage=75.0", \
19
+ "-XX:+UseG1GC", \
20
+ "-XX:+UseStringDeduplication", \
21
+ "-Djava.security.egd=file:/dev/./urandom", \
22
+ "-Dspring.backgroundpreinitializer.ignore=true", \
23
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.alpine-jlink ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-minimal Alpine + Custom JRE
2
+ # Expected size: ~120-160MB
3
+
4
+ # Stage 1: Create custom JRE with only needed modules
5
+ FROM eclipse-temurin:21-jdk-alpine as jre-builder
6
+ WORKDIR /app
7
+
8
+ # Analyze JAR to find required modules
9
+ COPY build/libs/*.jar app.jar
10
+ RUN jdeps --ignore-missing-deps --print-module-deps app.jar > modules.txt
11
+
12
+ # Create minimal JRE with only required modules
13
+ RUN jlink \
14
+ --add-modules $(cat modules.txt),java.logging,java.xml,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
15
+ --strip-debug \
16
+ --no-man-pages \
17
+ --no-header-files \
18
+ --compress=2 \
19
+ --output /custom-jre
20
+
21
+ # Stage 2: Production image
22
+ FROM alpine:3.19
23
+ RUN apk add --no-cache tzdata && \
24
+ addgroup -g 1001 -S appgroup && \
25
+ adduser -u 1001 -S appuser -G appgroup
26
+
27
+ # Copy custom JRE
28
+ COPY --from=jre-builder /custom-jre /opt/java
29
+ ENV JAVA_HOME=/opt/java
30
+ ENV PATH="$JAVA_HOME/bin:$PATH"
31
+
32
+ WORKDIR /app
33
+ COPY build/libs/*.jar app.jar
34
+ RUN chown appuser:appgroup app.jar
35
+
36
+ USER appuser
37
+ EXPOSE 8080
38
+
39
+ ENTRYPOINT ["java", \
40
+ "-XX:+UseContainerSupport", \
41
+ "-XX:MaxRAMPercentage=70.0", \
42
+ "-XX:+UseG1GC", \
43
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.layered ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-optimized layered build using Distroless
2
+ # Expected size: ~180-220MB with better caching
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot as base
5
+
6
+ # Stage 1: Extract JAR layers for optimal caching
7
+ FROM eclipse-temurin:21-jdk-alpine as extractor
8
+ WORKDIR /app
9
+ COPY build/libs/*.jar app.jar
10
+ RUN java -Djarmode=layertools -jar app.jar extract
11
+
12
+ # Stage 2: Production image with extracted layers
13
+ FROM base
14
+ WORKDIR /app
15
+
16
+ # Copy layers in dependency order (best caching)
17
+ COPY --from=extractor /app/dependencies/ ./
18
+ COPY --from=extractor /app/spring-boot-loader/ ./
19
+ COPY --from=extractor /app/snapshot-dependencies/ ./
20
+ COPY --from=extractor /app/application/ ./
21
+
22
+ EXPOSE 8080
23
+
24
+ # Optimized JVM settings for micro-containers
25
+ ENTRYPOINT ["java", \
26
+ "-XX:+UseContainerSupport", \
27
+ "-XX:MaxRAMPercentage=70.0", \
28
+ "-XX:+UseG1GC", \
29
+ "-XX:+UseStringDeduplication", \
30
+ "-XX:+CompactStrings", \
31
+ "-Xshare:on", \
32
+ "-Djava.security.egd=file:/dev/./urandom", \
33
+ "-Dspring.backgroundpreinitializer.ignore=true", \
34
+ "org.springframework.boot.loader.JarLauncher"]
src/main/docker/Dockerfile.native ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GraalVM Native Image - Ultra-fast startup, tiny size
2
+ # Expected size: ~50-80MB, startup <100ms
3
+ # Note: Requires native compilation support in Spring Boot
4
+
5
+ # Stage 1: Native compilation
6
+ FROM ghcr.io/graalvm/graalvm-ce:ol9-java21 as native-builder
7
+ WORKDIR /app
8
+
9
+ # Install native-image
10
+ RUN gu install native-image
11
+
12
+ # Copy source and build native executable
13
+ COPY . .
14
+ RUN ./gradlew nativeCompile
15
+
16
+ # Stage 2: Minimal runtime
17
+ FROM scratch
18
+ COPY --from=native-builder /app/build/native/nativeCompile/app /app
19
+ EXPOSE 8080
20
+ ENTRYPOINT ["/app"]
src/main/java/com/dalab/adminservice/DaAdminServiceApplication.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+ import org.springframework.cloud.openfeign.EnableFeignClients;
6
+
7
+ @SpringBootApplication
8
+ @EnableFeignClients // Important for enabling Feign clients
9
+ public class DaAdminServiceApplication {
10
+
11
+ public static void main(String[] args) {
12
+ SpringApplication.run(DaAdminServiceApplication.class, args);
13
+ }
14
+
15
+ }
src/main/java/com/dalab/adminservice/client/IAutoarchivalTaskApiClient.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.client;
2
+
3
+ import java.util.List;
4
+
5
+ import org.springframework.cloud.openfeign.FeignClient;
6
+ import org.springframework.web.bind.annotation.GetMapping;
7
+
8
+ import com.dalab.adminservice.dto.JobStatusDTO;
9
+
10
+ /**
11
+ * Feign client for Autoarchival Task API
12
+ */
13
+ @FeignClient(name = "da-autoarchival", url = "${dalab.services.autoarchival.url:http://localhost:8086}")
14
+ public interface IAutoarchivalTaskApiClient {
15
+
16
+ /**
17
+ * Get all archival tasks status
18
+ * @return List of job status DTOs
19
+ */
20
+ @GetMapping("/api/v1/autoarchival/archival/tasks")
21
+ List<JobStatusDTO> getArchivalTasks();
22
+ }
src/main/java/com/dalab/adminservice/client/IAutocomplianceJobApiClient.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.client;
2
+
3
+ import com.dalab.adminservice.dto.JobStatusDTO;
4
+ import org.springframework.cloud.openfeign.FeignClient;
5
+ import org.springframework.web.bind.annotation.GetMapping;
6
+ import org.springframework.web.bind.annotation.PathVariable;
7
+
8
+ import java.util.List;
9
+
10
+ @FeignClient(name = "autocompliance-service", url = "${feign.client.config.autocompliance-service.url:/api/v1/compliance}") // Placeholder URL
11
+ public interface IAutocomplianceJobApiClient {
12
+
13
+ // Assuming an endpoint like /api/v1/compliance/reports/jobs exists or will be created
14
+ @GetMapping("/reports/jobs")
15
+ List<JobStatusDTO> getComplianceReportJobs();
16
+
17
+ @GetMapping("/reports/jobs/{jobId}")
18
+ JobStatusDTO getComplianceReportJobById(@PathVariable("jobId") String jobId);
19
+ }
src/main/java/com/dalab/adminservice/client/IAutodeleteTaskApiClient.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.client;
2
+
3
+ import java.util.List;
4
+
5
+ import org.springframework.cloud.openfeign.FeignClient;
6
+ import org.springframework.web.bind.annotation.GetMapping;
7
+
8
+ import com.dalab.adminservice.dto.JobStatusDTO;
9
+
10
+ /**
11
+ * Feign client for Autodelete Task API
12
+ */
13
+ @FeignClient(name = "da-autodelete", url = "${dalab.services.autodelete.url:http://localhost:8087}")
14
+ public interface IAutodeleteTaskApiClient {
15
+
16
+ /**
17
+ * Get all deletion tasks status
18
+ * @return List of job status DTOs
19
+ */
20
+ @GetMapping("/api/v1/autodelete/deletion/tasks")
21
+ List<JobStatusDTO> getDeletionTasks();
22
+ }
src/main/java/com/dalab/adminservice/client/IAutolabelJobApiClient.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.client;
2
+
3
+ import java.util.List;
4
+
5
+ import org.springframework.cloud.openfeign.FeignClient;
6
+ import org.springframework.web.bind.annotation.GetMapping;
7
+
8
+ import com.dalab.adminservice.dto.JobStatusDTO;
9
+
10
+ /**
11
+ * Feign client for Autolabel Job API
12
+ */
13
+ @FeignClient(name = "da-autolabel", url = "${dalab.services.autolabel.url:http://localhost:8085}")
14
+ public interface IAutolabelJobApiClient {
15
+
16
+ /**
17
+ * Get all labeling jobs status
18
+ * @return List of job status DTOs
19
+ */
20
+ @GetMapping("/api/v1/autolabel/labeling/jobs")
21
+ List<JobStatusDTO> getAutolabelJobs();
22
+ }
src/main/java/com/dalab/adminservice/client/IDiscoveryJobApiClient.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.client;
2
+
3
+ import com.dalab.adminservice.dto.JobStatusDTO;
4
+ import org.springframework.cloud.openfeign.FeignClient;
5
+ import org.springframework.web.bind.annotation.GetMapping;
6
+ import org.springframework.web.bind.annotation.PathVariable;
7
+
8
+ import java.util.List;
9
+
10
+ // The name "discovery-service" should match a Feign client configuration in application.properties
11
+ // e.g., feign.client.config.discovery-service.url=http://da-discovery-service-host:port/api/v1/discovery
12
+ // The path here is relative to the URL configured for the Feign client.
13
+ @FeignClient(name = "discovery-service", url = "${feign.client.config.discovery-service.url:/api/v1/discovery}") // Placeholder URL
14
+ public interface IDiscoveryJobApiClient {
15
+
16
+ // Assuming da-discovery exposes an endpoint like /api/v1/discovery/jobs that returns a list of its jobs
17
+ // and that its Job DTO is compatible or can be mapped to our common JobStatusDTO
18
+ @GetMapping("/jobs") // This path is relative to the Feign client's configured URL
19
+ List<JobStatusDTO> getDiscoveryJobs();
20
+
21
+ @GetMapping("/jobs/{jobId}")
22
+ JobStatusDTO getDiscoveryJobById(@PathVariable("jobId") String jobId);
23
+ }
src/main/java/com/dalab/adminservice/config/KeycloakAdminClientConfig.java ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.config;
2
+
3
+ import org.keycloak.admin.client.Keycloak;
4
+ import org.keycloak.admin.client.KeycloakBuilder;
5
+ import org.springframework.beans.factory.annotation.Value;
6
+ import org.springframework.context.annotation.Bean;
7
+ import org.springframework.context.annotation.Configuration;
8
+
9
+ @Configuration
10
+ public class KeycloakAdminClientConfig {
11
+
12
+ @Value("${keycloak.auth-server-url}")
13
+ private String serverUrl;
14
+
15
+ @Value("${keycloak.realm}")
16
+ private String realm;
17
+
18
+ @Value("${keycloak.client-id}")
19
+ private String clientId;
20
+
21
+ @Value("${keycloak.client-secret:#{null}}") // Default to null if not provided
22
+ private String clientSecret;
23
+
24
+ // Optional: If using password credentials grant type for admin client
25
+ @Value("${keycloak.admin.username:#{null}}")
26
+ private String adminUsername;
27
+
28
+ @Value("${keycloak.admin.password:#{null}}")
29
+ private String adminPassword;
30
+
31
+ @Bean
32
+ public Keycloak keycloakAdminClient() {
33
+ KeycloakBuilder builder = KeycloakBuilder.builder()
34
+ .serverUrl(serverUrl)
35
+ .realm(realm)
36
+ .clientId(clientId);
37
+ // ResteasyClient can be configured here if needed, e.g., for connection pooling
38
+ // .resteasyClient(ResteasyClientBuilder.newBuilder().build()); // Basic Resteasy client
39
+
40
+ if (clientSecret != null && !clientSecret.isEmpty()) {
41
+ builder.clientSecret(clientSecret);
42
+ // Ensure the client (e.g., admin-cli or your dedicated client) has "Service Accounts Enabled"
43
+ // and appropriate "Service Account Roles" (e.g., realm-management -> manage-users, view-users)
44
+ builder.grantType("client_credentials");
45
+ } else if (adminUsername != null && !adminUsername.isEmpty() && adminPassword != null && !adminPassword.isEmpty()){
46
+ // This is typically for direct admin user login, ensure this user has necessary permissions.
47
+ // The 'admin-cli' client itself might not allow password grant for other users.
48
+ // Prefer service account with client_credentials for backend services.
49
+ builder.username(adminUsername);
50
+ builder.password(adminPassword);
51
+ builder.grantType("password");
52
+ } else {
53
+ throw new IllegalStateException("Keycloak admin client not properly configured: Missing clientSecret or admin username/password.");
54
+ }
55
+
56
+ return builder.build();
57
+ }
58
+ }
src/main/java/com/dalab/adminservice/config/SpringSecurityAuditorAware.java ADDED
@@ -0,0 +1 @@
 
 
1
+
src/main/java/com/dalab/adminservice/controller/CloudConnectionController.java ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.controller;
2
+
3
+ import java.util.List;
4
+ import java.util.Optional;
5
+ import java.util.UUID;
6
+
7
+ import org.springframework.http.HttpStatus;
8
+ import org.springframework.http.ResponseEntity;
9
+ import org.springframework.security.access.prepost.PreAuthorize;
10
+ import org.springframework.web.bind.annotation.DeleteMapping;
11
+ import org.springframework.web.bind.annotation.GetMapping;
12
+ import org.springframework.web.bind.annotation.PathVariable;
13
+ import org.springframework.web.bind.annotation.PostMapping;
14
+ import org.springframework.web.bind.annotation.PutMapping;
15
+ import org.springframework.web.bind.annotation.RequestBody;
16
+ import org.springframework.web.bind.annotation.RequestMapping;
17
+ import org.springframework.web.bind.annotation.RestController;
18
+
19
+ import com.dalab.adminservice.dto.CloudConnectionDTO;
20
+ import com.dalab.adminservice.dto.CloudConnectionTestResultDTO;
21
+ import com.dalab.adminservice.service.ICloudConnectionService;
22
+
23
+ import io.swagger.v3.oas.annotations.Operation;
24
+ import io.swagger.v3.oas.annotations.tags.Tag;
25
+ import jakarta.validation.Valid;
26
+ import lombok.RequiredArgsConstructor;
27
+ import lombok.extern.slf4j.Slf4j;
28
+
29
+ /**
30
+ * REST controller for managing cloud connections
31
+ */
32
+ @RestController
33
+ @RequestMapping("/api/v1/admin/cloud-connections")
34
+ @RequiredArgsConstructor
35
+ @Slf4j
36
+ @Tag(name = "Cloud Connection Management", description = "APIs for managing cloud connections")
37
+ public class CloudConnectionController {
38
+
39
+ private final ICloudConnectionService cloudConnectionService;
40
+
41
+ @GetMapping
42
+ @PreAuthorize("hasRole('ADMIN')")
43
+ @Operation(summary = "Get all cloud connections")
44
+ public ResponseEntity<List<CloudConnectionDTO>> getAllCloudConnections() {
45
+ log.debug("Getting all cloud connections");
46
+ List<CloudConnectionDTO> connections = cloudConnectionService.getAllCloudConnections();
47
+ return ResponseEntity.ok(connections);
48
+ }
49
+
50
+ @GetMapping("/{connectionId}")
51
+ @PreAuthorize("hasRole('ADMIN')")
52
+ @Operation(summary = "Get cloud connection by ID")
53
+ public ResponseEntity<CloudConnectionDTO> getCloudConnectionById(@PathVariable String connectionId) {
54
+ log.debug("Getting cloud connection with id: {}", connectionId);
55
+ Optional<CloudConnectionDTO> connection = cloudConnectionService.getCloudConnectionById(connectionId);
56
+ return connection.map(ResponseEntity::ok)
57
+ .orElse(ResponseEntity.notFound().build());
58
+ }
59
+
60
+ @PostMapping
61
+ @PreAuthorize("hasRole('ADMIN')")
62
+ @Operation(summary = "Create new cloud connection")
63
+ public ResponseEntity<CloudConnectionDTO> createCloudConnection(@Valid @RequestBody CloudConnectionDTO cloudConnectionDTO) {
64
+ log.debug("Creating new cloud connection: {}", cloudConnectionDTO.getName());
65
+ CloudConnectionDTO created = cloudConnectionService.createCloudConnection(cloudConnectionDTO);
66
+ return ResponseEntity.status(HttpStatus.CREATED).body(created);
67
+ }
68
+
69
+ @PutMapping("/{connectionId}")
70
+ @PreAuthorize("hasRole('ADMIN')")
71
+ @Operation(summary = "Update cloud connection")
72
+ public ResponseEntity<CloudConnectionDTO> updateCloudConnection(
73
+ @PathVariable String connectionId,
74
+ @Valid @RequestBody CloudConnectionDTO cloudConnectionDTO) {
75
+ log.debug("Updating cloud connection with id: {}", connectionId);
76
+ CloudConnectionDTO updated = cloudConnectionService.updateCloudConnection(connectionId, cloudConnectionDTO);
77
+ return ResponseEntity.ok(updated);
78
+ }
79
+
80
+ @DeleteMapping("/{connectionId}")
81
+ @PreAuthorize("hasRole('ADMIN')")
82
+ @Operation(summary = "Delete cloud connection")
83
+ public ResponseEntity<Void> deleteCloudConnection(@PathVariable String connectionId) {
84
+ log.debug("Deleting cloud connection with id: {}", connectionId);
85
+ cloudConnectionService.deleteCloudConnection(connectionId);
86
+ return ResponseEntity.noContent().build();
87
+ }
88
+
89
+ @PostMapping("/{connectionId}/test")
90
+ @PreAuthorize("hasRole('ADMIN')")
91
+ @Operation(summary = "Test cloud connection")
92
+ public ResponseEntity<CloudConnectionTestResultDTO> testCloudConnection(@PathVariable String connectionId) {
93
+ log.debug("Testing cloud connection with id: {}", connectionId);
94
+ CloudConnectionTestResultDTO result = cloudConnectionService.testCloudConnection(connectionId);
95
+ return ResponseEntity.ok(result);
96
+ }
97
+ }
src/main/java/com/dalab/adminservice/controller/JobStatusController.java ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.controller;
2
+
3
+ import com.dalab.adminservice.dto.AggregatedJobStatusDTO;
4
+ import com.dalab.adminservice.service.IJobStatusService;
5
+ import io.swagger.v3.oas.annotations.Operation;
6
+ import io.swagger.v3.oas.annotations.responses.ApiResponse;
7
+ import io.swagger.v3.oas.annotations.tags.Tag;
8
+ import lombok.RequiredArgsConstructor;
9
+ import org.springframework.http.ResponseEntity;
10
+ import org.springframework.security.access.prepost.PreAuthorize;
11
+ import org.springframework.web.bind.annotation.GetMapping;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RestController;
14
+
15
+ @RestController
16
+ @RequestMapping("/api/v1/admin/job-statuses")
17
+ @RequiredArgsConstructor
18
+ @Tag(name = "Job Status Management", description = "APIs for viewing aggregated job statuses across services.")
19
+ public class JobStatusController {
20
+
21
+ private final IJobStatusService jobStatusService;
22
+
23
+ @GetMapping
24
+ @PreAuthorize("hasAnyRole('ADMIN', 'VIEWER')") // Or more specific roles as needed
25
+ @Operation(summary = "Get Aggregated Job Statuses",
26
+ description = "Retrieves a list of all jobs from various microservices and their statuses.",
27
+ responses = {
28
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved job statuses"),
29
+ @ApiResponse(responseCode = "500", description = "Internal server error while fetching statuses")
30
+ })
31
+ public ResponseEntity<AggregatedJobStatusDTO> getAggregatedJobStatuses() {
32
+ AggregatedJobStatusDTO aggregatedStatus = jobStatusService.getAggregatedJobStatuses();
33
+ return ResponseEntity.ok(aggregatedStatus);
34
+ }
35
+ }
src/main/java/com/dalab/adminservice/controller/RoleController.java ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.controller;
2
+
3
+ import com.dalab.adminservice.dto.RoleDTO;
4
+ import com.dalab.adminservice.service.IRoleService;
5
+ import io.swagger.v3.oas.annotations.Operation;
6
+ import io.swagger.v3.oas.annotations.responses.ApiResponse;
7
+ import io.swagger.v3.oas.annotations.tags.Tag;
8
+ import lombok.RequiredArgsConstructor;
9
+ import org.springframework.http.ResponseEntity;
10
+ import org.springframework.security.access.prepost.PreAuthorize;
11
+ import org.springframework.web.bind.annotation.GetMapping;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RestController;
14
+
15
+ import java.util.List;
16
+
17
+ @RestController
18
+ @RequestMapping("/api/v1/admin/roles")
19
+ @RequiredArgsConstructor
20
+ @Tag(name = "Role Management", description = "APIs for viewing available roles.")
21
+ @PreAuthorize("hasRole('ADMIN')")
22
+ public class RoleController {
23
+
24
+ private final IRoleService roleService;
25
+
26
+ @GetMapping
27
+ @Operation(summary = "Get all available realm roles",
28
+ description = "Retrieves a list of all available roles in the configured Keycloak realm.",
29
+ responses = @ApiResponse(responseCode = "200", description = "Successfully retrieved roles"))
30
+ public ResponseEntity<List<RoleDTO>> getAllRealmRoles() {
31
+ return ResponseEntity.ok(roleService.getAllRealmRoles());
32
+ }
33
+ }
src/main/java/com/dalab/adminservice/controller/ServiceConfigController.java ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.controller;
2
+
3
+ import java.util.List;
4
+ import java.util.Optional;
5
+
6
+ import org.springframework.http.HttpStatus;
7
+ import org.springframework.http.ResponseEntity;
8
+ import org.springframework.security.access.prepost.PreAuthorize;
9
+ import org.springframework.web.bind.annotation.GetMapping;
10
+ import org.springframework.web.bind.annotation.PathVariable;
11
+ import org.springframework.web.bind.annotation.PostMapping;
12
+ import org.springframework.web.bind.annotation.PutMapping;
13
+ import org.springframework.web.bind.annotation.RequestBody;
14
+ import org.springframework.web.bind.annotation.RequestMapping;
15
+ import org.springframework.web.bind.annotation.RestController;
16
+
17
+ import com.dalab.adminservice.dto.ServiceConfigDTO;
18
+ import com.dalab.adminservice.service.IServiceConfigService;
19
+
20
+ import io.swagger.v3.oas.annotations.Operation;
21
+ import io.swagger.v3.oas.annotations.tags.Tag;
22
+ import jakarta.validation.Valid;
23
+ import lombok.RequiredArgsConstructor;
24
+ import lombok.extern.slf4j.Slf4j;
25
+
26
+ /**
27
+ * REST controller for managing service configurations
28
+ */
29
+ @RestController
30
+ @RequestMapping("/api/v1/admin/config/services")
31
+ @RequiredArgsConstructor
32
+ @Slf4j
33
+ @Tag(name = "Service Configuration Management", description = "APIs for managing service configurations")
34
+ public class ServiceConfigController {
35
+
36
+ private final IServiceConfigService serviceConfigService;
37
+
38
+ @GetMapping
39
+ @PreAuthorize("hasRole('ADMIN')")
40
+ @Operation(summary = "Get all service configurations")
41
+ public ResponseEntity<List<ServiceConfigDTO>> getAllServiceConfigs() {
42
+ log.debug("Getting all service configurations");
43
+ List<ServiceConfigDTO> configs = serviceConfigService.getAllServiceConfigs();
44
+ return ResponseEntity.ok(configs);
45
+ }
46
+
47
+ @GetMapping("/{serviceId}")
48
+ @PreAuthorize("hasRole('ADMIN')")
49
+ @Operation(summary = "Get service configuration by service ID")
50
+ public ResponseEntity<ServiceConfigDTO> getServiceConfigById(@PathVariable String serviceId) {
51
+ log.debug("Getting service configuration for service: {}", serviceId);
52
+ Optional<ServiceConfigDTO> config = serviceConfigService.getServiceConfigById(serviceId);
53
+ return config.map(ResponseEntity::ok)
54
+ .orElse(ResponseEntity.notFound().build());
55
+ }
56
+
57
+ @PostMapping
58
+ @PreAuthorize("hasRole('ADMIN')")
59
+ @Operation(summary = "Create new service configuration")
60
+ public ResponseEntity<ServiceConfigDTO> createServiceConfig(@Valid @RequestBody ServiceConfigDTO serviceConfigDTO) {
61
+ log.debug("Creating new service configuration for service: {}", serviceConfigDTO.getServiceId());
62
+ ServiceConfigDTO created = serviceConfigService.createServiceConfig(serviceConfigDTO);
63
+ return ResponseEntity.status(HttpStatus.CREATED).body(created);
64
+ }
65
+
66
+ @PutMapping("/{serviceId}")
67
+ @PreAuthorize("hasRole('ADMIN')")
68
+ @Operation(summary = "Update service configuration")
69
+ public ResponseEntity<ServiceConfigDTO> updateServiceConfig(
70
+ @PathVariable String serviceId,
71
+ @Valid @RequestBody ServiceConfigDTO serviceConfigDTO) {
72
+ log.debug("Updating service configuration for service: {}", serviceId);
73
+ ServiceConfigDTO updated = serviceConfigService.updateServiceConfig(serviceId, serviceConfigDTO);
74
+ return ResponseEntity.ok(updated);
75
+ }
76
+ }
src/main/java/com/dalab/adminservice/controller/UserController.java ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.controller;
2
+
3
+ import com.dalab.adminservice.dto.UserDTO;
4
+ import com.dalab.adminservice.service.IUserService;
5
+ import io.swagger.v3.oas.annotations.Operation;
6
+ import io.swagger.v3.oas.annotations.Parameter;
7
+ import io.swagger.v3.oas.annotations.responses.ApiResponse;
8
+ import io.swagger.v3.oas.annotations.tags.Tag;
9
+ import jakarta.validation.Valid;
10
+ import lombok.RequiredArgsConstructor;
11
+ import org.keycloak.representations.idm.RoleRepresentation;
12
+ import org.springframework.http.HttpStatus;
13
+ import org.springframework.http.ResponseEntity;
14
+ import org.springframework.security.access.prepost.PreAuthorize;
15
+ import org.springframework.web.bind.annotation.*;
16
+
17
+ import java.util.List;
18
+
19
+ @RestController
20
+ @RequestMapping("/api/v1/admin/users")
21
+ @RequiredArgsConstructor
22
+ @Tag(name = "User Management", description = "APIs for managing user accounts.")
23
+ @PreAuthorize("hasRole('ADMIN')") // All user management operations require ADMIN role
24
+ public class UserController {
25
+
26
+ private final IUserService userService;
27
+
28
+ @GetMapping
29
+ @Operation(summary = "Get all users",
30
+ description = "Retrieves a paginated list of all users.",
31
+ responses = @ApiResponse(responseCode = "200", description = "Successfully retrieved users"))
32
+ public ResponseEntity<List<UserDTO>> getAllUsers(
33
+ @Parameter(description = "Offset for pagination") @RequestParam(required = false) Integer firstResult,
34
+ @Parameter(description = "Maximum number of results per page") @RequestParam(required = false) Integer maxResults) {
35
+ return ResponseEntity.ok(userService.getAllUsers(firstResult, maxResults));
36
+ }
37
+
38
+ @GetMapping("/{userId}")
39
+ @Operation(summary = "Get user by ID",
40
+ description = "Retrieves a specific user by their ID.",
41
+ responses = {
42
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved user"),
43
+ @ApiResponse(responseCode = "404", description = "User not found")
44
+ })
45
+ public ResponseEntity<UserDTO> getUserById(
46
+ @Parameter(description = "ID of the user to retrieve", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479")
47
+ @PathVariable String userId) {
48
+ return userService.getUserById(userId)
49
+ .map(ResponseEntity::ok)
50
+ .orElse(ResponseEntity.notFound().build());
51
+ }
52
+
53
+ @GetMapping("/username/{username}")
54
+ @Operation(summary = "Get user by username",
55
+ description = "Retrieves a specific user by their username.",
56
+ responses = {
57
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved user"),
58
+ @ApiResponse(responseCode = "404", description = "User not found")
59
+ })
60
+ public ResponseEntity<UserDTO> getUserByUsername(
61
+ @Parameter(description = "Username of the user to retrieve", example = "johndoe")
62
+ @PathVariable String username) {
63
+ return userService.getUserByUsername(username)
64
+ .map(ResponseEntity::ok)
65
+ .orElse(ResponseEntity.notFound().build());
66
+ }
67
+
68
+ @PostMapping
69
+ @Operation(summary = "Create a new user",
70
+ description = "Creates a new user account.",
71
+ responses = {
72
+ @ApiResponse(responseCode = "201", description = "User created successfully"),
73
+ @ApiResponse(responseCode = "400", description = "Invalid input data"),
74
+ @ApiResponse(responseCode = "409", description = "User already exists")
75
+ })
76
+ public ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserDTO userDTO) {
77
+ UserDTO createdUser = userService.createUser(userDTO);
78
+ return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
79
+ }
80
+
81
+ @PutMapping("/{userId}")
82
+ @Operation(summary = "Update an existing user",
83
+ description = "Updates an existing user account.",
84
+ responses = {
85
+ @ApiResponse(responseCode = "200", description = "User updated successfully"),
86
+ @ApiResponse(responseCode = "400", description = "Invalid input data"),
87
+ @ApiResponse(responseCode = "404", description = "User not found")
88
+ })
89
+ public ResponseEntity<UserDTO> updateUser(
90
+ @Parameter(description = "ID of the user to update") @PathVariable String userId,
91
+ @Valid @RequestBody UserDTO userDTO) {
92
+ UserDTO updatedUser = userService.updateUser(userId, userDTO);
93
+ return ResponseEntity.ok(updatedUser);
94
+ }
95
+
96
+ @DeleteMapping("/{userId}")
97
+ @Operation(summary = "Delete a user",
98
+ description = "Deletes a user account by their ID.",
99
+ responses = {
100
+ @ApiResponse(responseCode = "204", description = "User deleted successfully"),
101
+ @ApiResponse(responseCode = "404", description = "User not found")
102
+ })
103
+ public ResponseEntity<Void> deleteUser(
104
+ @Parameter(description = "ID of the user to delete") @PathVariable String userId) {
105
+ userService.deleteUser(userId);
106
+ return ResponseEntity.noContent().build();
107
+ }
108
+
109
+ // Role Management specific to a user might be better here than a top-level /roles endpoint if it's always user-centric
110
+ @GetMapping("/{userId}/roles")
111
+ @Operation(summary = "Get user's realm roles",
112
+ description = "Retrieves the realm roles assigned to a specific user.")
113
+ public ResponseEntity<List<RoleRepresentation>> getUserRealmRoles(@PathVariable String userId) {
114
+ return ResponseEntity.ok(userService.getUserRealmRoles(userId));
115
+ }
116
+
117
+ @PostMapping("/{userId}/roles")
118
+ @Operation(summary = "Assign realm roles to user",
119
+ description = "Assigns a list of realm roles to a specific user. Replaces existing roles if not additive.")
120
+ @ResponseStatus(HttpStatus.NO_CONTENT)
121
+ public void assignRealmRolesToUser(
122
+ @PathVariable String userId,
123
+ @Parameter(description = "List of role names to assign") @RequestBody List<String> roleNames) {
124
+ userService.assignRealmRolesToUser(userId, roleNames);
125
+ }
126
+ }
src/main/java/com/dalab/adminservice/dto/AggregatedJobStatusDTO.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Builder;
5
+ import lombok.Data;
6
+ import lombok.NoArgsConstructor;
7
+
8
+ import java.util.List;
9
+
10
+ @Data
11
+ @Builder
12
+ @NoArgsConstructor
13
+ @AllArgsConstructor
14
+ public class AggregatedJobStatusDTO {
15
+ private List<JobStatusDTO> jobs;
16
+ private int totalJobs;
17
+ private int pendingJobs;
18
+ private int runningJobs;
19
+ private int completedSuccessJobs;
20
+ private int completedFailedJobs;
21
+ // Add any other summary fields as needed
22
+ }
src/main/java/com/dalab/adminservice/dto/CloudConnectionDTO.java ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import java.util.Map;
4
+
5
+ import com.dalab.adminservice.model.enums.CloudProviderType;
6
+
7
+ import io.swagger.v3.oas.annotations.media.Schema;
8
+ import jakarta.validation.constraints.NotBlank;
9
+ import jakarta.validation.constraints.NotNull;
10
+ import jakarta.validation.constraints.Size;
11
+ import lombok.AllArgsConstructor;
12
+ import lombok.Builder;
13
+ import lombok.Data;
14
+ import lombok.NoArgsConstructor;
15
+
16
+ @Data
17
+ @Builder
18
+ @NoArgsConstructor
19
+ @AllArgsConstructor
20
+ @Schema(description = "Represents a cloud connection configuration.")
21
+ public class CloudConnectionDTO {
22
+
23
+ @Schema(description = "Unique identifier of the cloud connection, generated by the system.", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479", accessMode = Schema.AccessMode.READ_ONLY)
24
+ private String id;
25
+
26
+ @NotBlank
27
+ @Size(max = 100)
28
+ @Schema(description = "User-defined name for this cloud connection.", example = "My Production GCP Connection")
29
+ private String name;
30
+
31
+ @Schema(description = "Optional description for the cloud connection.", example = "Main GCP project for production workloads")
32
+ private String description;
33
+
34
+ @NotNull
35
+ @Schema(description = "Type of the cloud provider.", example = "GCP")
36
+ private CloudProviderType providerType;
37
+
38
+ @Schema(description = "Key-value pairs for connection credentials and parameters (e.g., projectId, region, accessKey). Specific keys depend on the providerType. Sensitive values are write-only.")
39
+ private Map<String, String> connectionParameters; // e.g., { "projectId": "my-gcp-project", "region": "us-central1" }
40
+
41
+ @Schema(description = "Service Account Key JSON or other sensitive credential details. This field is write-only and will not be returned in GET requests.", accessMode = Schema.AccessMode.WRITE_ONLY)
42
+ private String sensitiveCredentials; // e.g., GCP Service Account JSON, AWS Secret Key
43
+
44
+ @Schema(description = "Indicates if the connection is currently enabled.", example = "true", accessMode = Schema.AccessMode.READ_ONLY)
45
+ @Builder.Default
46
+ private boolean enabled = true;
47
+
48
+ @Schema(description = "Timestamp of the last successful connection test.", accessMode = Schema.AccessMode.READ_ONLY)
49
+ private String lastConnectionTestAt; // Using String for simplicity, can be LocalDateTime
50
+
51
+ @Schema(description = "Status of the last connection test (e.g., SUCCESS, FAILED).", accessMode = Schema.AccessMode.READ_ONLY)
52
+ private String lastConnectionTestStatus;
53
+ }
src/main/java/com/dalab/adminservice/dto/CloudConnectionTestResultDTO.java ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import io.swagger.v3.oas.annotations.media.Schema;
4
+ import lombok.AllArgsConstructor;
5
+ import lombok.Builder;
6
+ import lombok.Data;
7
+ import lombok.NoArgsConstructor;
8
+
9
+ @Data
10
+ @Builder
11
+ @NoArgsConstructor
12
+ @AllArgsConstructor
13
+ @Schema(description = "Result of a cloud connection test operation.")
14
+ public class CloudConnectionTestResultDTO {
15
+
16
+ @Schema(description = "Indicates whether the connection test was successful.", example = "true")
17
+ private boolean success;
18
+
19
+ @Schema(description = "A message detailing the test result, including error information if applicable.", example = "Connection to GCP project 'my-gcp-project' successful.")
20
+ private String message;
21
+
22
+ @Schema(description = "Timestamp of when the test was performed.")
23
+ private String testedAt; // Can be LocalDateTime
24
+ }
src/main/java/com/dalab/adminservice/dto/JobStatusDTO.java ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Builder;
5
+ import lombok.Data;
6
+ import lombok.NoArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+ import java.util.Map;
10
+
11
+ @Data
12
+ @Builder
13
+ @NoArgsConstructor
14
+ @AllArgsConstructor
15
+ public class JobStatusDTO {
16
+ private String jobId;
17
+ private String serviceName; // Name of the service that owns the job
18
+ private String jobType; // e.g., "DISCOVERY_SCAN", "AUTOLABEL_TASK", "ARCHIVAL_JOB"
19
+ private String status; // e.g., "PENDING", "RUNNING", "COMPLETED_SUCCESS", "COMPLETED_FAILED", "UNKNOWN"
20
+ private LocalDateTime submittedAt;
21
+ private LocalDateTime startedAt;
22
+ private LocalDateTime completedAt;
23
+ private Map<String, Object> details; // Any service-specific details about the job
24
+ private String errorDetails; // Error message if the job failed
25
+ }
src/main/java/com/dalab/adminservice/dto/RoleDTO.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Builder;
5
+ import lombok.Data;
6
+ import lombok.NoArgsConstructor;
7
+
8
+ /**
9
+ * DTO for role management operations.
10
+ */
11
+ @Data
12
+ @Builder
13
+ @NoArgsConstructor
14
+ @AllArgsConstructor
15
+ public class RoleDTO {
16
+
17
+ private String id;
18
+ private String name;
19
+ private String description;
20
+
21
+ }
src/main/java/com/dalab/adminservice/dto/ServiceConfigDTO.java ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import jakarta.validation.constraints.NotBlank;
4
+ import lombok.AllArgsConstructor;
5
+ import lombok.Builder;
6
+ import lombok.Data;
7
+ import lombok.NoArgsConstructor;
8
+
9
+ /**
10
+ * DTO for service configuration management.
11
+ */
12
+ @Data
13
+ @Builder
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ public class ServiceConfigDTO {
17
+
18
+ @NotBlank
19
+ private String serviceId;
20
+
21
+ private String displayName;
22
+ private String description;
23
+ private String version;
24
+ private String endpoint;
25
+
26
+ @Builder.Default
27
+ private boolean enabled = true;
28
+
29
+ }
src/main/java/com/dalab/adminservice/dto/UserDTO.java ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.dto;
2
+
3
+ import java.util.List;
4
+
5
+ import jakarta.validation.constraints.Email;
6
+ import jakarta.validation.constraints.NotBlank;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+ import lombok.Data;
10
+ import lombok.NoArgsConstructor;
11
+
12
+ /**
13
+ * DTO for user management operations.
14
+ */
15
+ @Data
16
+ @Builder
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ public class UserDTO {
20
+
21
+ private String id;
22
+
23
+ @NotBlank
24
+ private String username;
25
+
26
+ @Email
27
+ private String email;
28
+
29
+ private String firstName;
30
+ private String lastName;
31
+ private String password;
32
+
33
+ @Builder.Default
34
+ private boolean enabled = true;
35
+
36
+ private List<String> roles;
37
+
38
+ }
src/main/java/com/dalab/adminservice/exception/ConflictException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.exception;
2
+
3
+ /**
4
+ * Exception thrown when a conflict occurs during operations.
5
+ */
6
+ public class ConflictException extends RuntimeException {
7
+
8
+ public ConflictException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ConflictException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/adminservice/exception/KeycloakAdminException.java ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.exception;
2
+
3
+ // Can be a generic runtime exception or extend a more specific one if needed
4
+ public class KeycloakAdminException extends RuntimeException {
5
+ public KeycloakAdminException(String message, Throwable cause) {
6
+ super(message, cause);
7
+ }
8
+ }
src/main/java/com/dalab/adminservice/exception/NotFoundException.java ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.exception;
2
+
3
+ import org.springframework.http.HttpStatus;
4
+ import org.springframework.web.bind.annotation.ResponseStatus;
5
+
6
+ @ResponseStatus(HttpStatus.NOT_FOUND)
7
+ public class NotFoundException extends RuntimeException {
8
+ public NotFoundException(String message) {
9
+ super(message);
10
+ }
11
+ }
src/main/java/com/dalab/adminservice/mapper/CloudConnectionMapper.java ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.mapper;
2
+
3
+ import com.dalab.adminservice.dto.CloudConnectionDTO;
4
+ import com.dalab.adminservice.model.CloudConnectionEntity;
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.Mapping;
7
+ import org.mapstruct.MappingTarget;
8
+ import org.mapstruct.NullValuePropertyMappingStrategy;
9
+ import org.mapstruct.ReportingPolicy;
10
+ import org.mapstruct.factory.Mappers;
11
+
12
+ import java.util.List;
13
+
14
+ /**
15
+ * Mapper interface for converting between CloudConnectionEntity and CloudConnectionDTO.
16
+ */
17
+ @Mapper(
18
+ componentModel = "spring",
19
+ unmappedTargetPolicy = ReportingPolicy.IGNORE,
20
+ nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
21
+ )
22
+ public interface CloudConnectionMapper {
23
+
24
+ CloudConnectionMapper INSTANCE = Mappers.getMapper(CloudConnectionMapper.class);
25
+
26
+ // When mapping from Entity to DTO, ignore encryptedCredentials
27
+ @Mapping(target = "sensitiveCredentials", ignore = true)
28
+ CloudConnectionDTO toDto(CloudConnectionEntity entity);
29
+
30
+ List<CloudConnectionDTO> toDtoList(List<CloudConnectionEntity> entities);
31
+
32
+ // When mapping from DTO to Entity for creation/update, sensitiveCredentials from DTO will be handled by service for encryption
33
+ // The encryptedCredentials field in entity will be set by the service, not directly by mapper from DTO.sensitiveCredentials
34
+ @Mapping(target = "id", ignore = true) // ID is generated or path param
35
+ @Mapping(target = "encryptedCredentials", ignore = true) // Handled by service
36
+ @Mapping(target = "createdBy", ignore = true)
37
+ @Mapping(target = "createdDate", ignore = true)
38
+ @Mapping(target = "lastModifiedBy", ignore = true)
39
+ @Mapping(target = "lastModifiedDate", ignore = true)
40
+ @Mapping(target = "lastConnectionTestAt", ignore = true) // These are set by the system
41
+ @Mapping(target = "lastConnectionTestStatus", ignore = true)
42
+ void updateEntityFromDto(CloudConnectionDTO dto, @MappingTarget CloudConnectionEntity entity);
43
+
44
+ @Mapping(target = "id", ignore = true) // ID is generated
45
+ @Mapping(target = "encryptedCredentials", ignore = true) // Handled by service
46
+ @Mapping(target = "createdBy", ignore = true)
47
+ @Mapping(target = "createdDate", ignore = true)
48
+ @Mapping(target = "lastModifiedBy", ignore = true)
49
+ @Mapping(target = "lastModifiedDate", ignore = true)
50
+ @Mapping(target = "lastConnectionTestAt", ignore = true)
51
+ @Mapping(target = "lastConnectionTestStatus", ignore = true)
52
+ CloudConnectionEntity toEntity(CloudConnectionDTO dto);
53
+ }
src/main/java/com/dalab/adminservice/mapper/RoleMapper.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.mapper;
2
+
3
+ import com.dalab.adminservice.dto.RoleDTO;
4
+ import org.keycloak.representations.idm.RoleRepresentation;
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.ReportingPolicy;
7
+
8
+ import java.util.List;
9
+
10
+ @Mapper(
11
+ componentModel = "spring",
12
+ unmappedTargetPolicy = ReportingPolicy.IGNORE
13
+ )
14
+ public interface RoleMapper {
15
+
16
+ RoleDTO toDto(RoleRepresentation roleRepresentation);
17
+
18
+ List<RoleDTO> toDtoList(List<RoleRepresentation> roleRepresentations);
19
+ }
src/main/java/com/dalab/adminservice/mapper/ServiceConfigMapper.java ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.mapper;
2
+
3
+ import java.util.List;
4
+
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.Mapping;
7
+ import org.mapstruct.MappingTarget;
8
+ import org.mapstruct.factory.Mappers;
9
+
10
+ import com.dalab.adminservice.dto.ServiceConfigDTO;
11
+ import com.dalab.adminservice.model.ServiceConfigEntity;
12
+
13
+ /**
14
+ * Mapper interface for converting between ServiceConfigEntity and ServiceConfigDTO.
15
+ */
16
+ @Mapper
17
+ public interface ServiceConfigMapper {
18
+
19
+ ServiceConfigMapper INSTANCE = Mappers.getMapper(ServiceConfigMapper.class);
20
+
21
+ ServiceConfigDTO toDto(ServiceConfigEntity entity);
22
+
23
+ List<ServiceConfigDTO> toDtoList(List<ServiceConfigEntity> entities);
24
+
25
+ @Mapping(target = "serviceId", ignore = true)
26
+ void updateEntityFromDto(ServiceConfigDTO dto, @MappingTarget ServiceConfigEntity entity);
27
+
28
+ ServiceConfigEntity toEntity(ServiceConfigDTO dto);
29
+ }
src/main/java/com/dalab/adminservice/mapper/UserMapper.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.mapper;
2
+
3
+ import com.dalab.adminservice.dto.UserDTO;
4
+ import org.keycloak.representations.idm.CredentialRepresentation;
5
+ import org.keycloak.representations.idm.UserRepresentation;
6
+ import org.mapstruct.Mapper;
7
+ import org.mapstruct.Mapping;
8
+ import org.mapstruct.Named;
9
+ import org.mapstruct.ReportingPolicy;
10
+
11
+ import java.util.Collections;
12
+ import java.util.List;
13
+
14
+ @Mapper(
15
+ componentModel = "spring",
16
+ unmappedTargetPolicy = ReportingPolicy.IGNORE
17
+ )
18
+ public interface UserMapper {
19
+
20
+ @Mapping(target = "roles", source = "realmRoles") // Assuming roles are stored in realmRoles
21
+ UserDTO toDto(UserRepresentation userRepresentation);
22
+
23
+ List<UserDTO> toDtoList(List<UserRepresentation> userRepresentations);
24
+
25
+ // For creating/updating users in Keycloak
26
+ @Mapping(target = "realmRoles", source = "roles")
27
+ @Mapping(target = "credentials", source = "password", qualifiedByName = "passwordToCredentials")
28
+ UserRepresentation toRepresentation(UserDTO userDTO);
29
+
30
+ @Named("passwordToCredentials")
31
+ default List<CredentialRepresentation> passwordToCredentials(String password) {
32
+ if (password == null || password.isEmpty()) {
33
+ return Collections.emptyList();
34
+ }
35
+ CredentialRepresentation credential = new CredentialRepresentation();
36
+ credential.setType(CredentialRepresentation.PASSWORD);
37
+ credential.setValue(password);
38
+ credential.setTemporary(false); // Set to true if password reset is required on first login
39
+ return Collections.singletonList(credential);
40
+ }
41
+ }
src/main/java/com/dalab/adminservice/model/AbstractAuditableEntity.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.model;
2
+
3
+ import jakarta.persistence.Column;
4
+ import jakarta.persistence.EntityListeners;
5
+ import jakarta.persistence.MappedSuperclass;
6
+ import lombok.Data;
7
+ import org.springframework.data.annotation.CreatedBy;
8
+ import org.springframework.data.annotation.CreatedDate;
9
+ import org.springframework.data.annotation.LastModifiedBy;
10
+ import org.springframework.data.annotation.LastModifiedDate;
11
+ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
12
+
13
+ import java.io.Serializable;
14
+ import java.time.Instant;
15
+
16
+ /**
17
+ * Abstract base class for auditable entities.
18
+ */
19
+ @Data
20
+ @MappedSuperclass
21
+ @EntityListeners(AuditingEntityListener.class)
22
+ public abstract class AbstractAuditableEntity implements Serializable {
23
+
24
+ private static final long serialVersionUID = 1L;
25
+
26
+ @CreatedBy
27
+ @Column(name = "created_by", nullable = false, length = 50, updatable = false)
28
+ private String createdBy;
29
+
30
+ @CreatedDate
31
+ @Column(name = "created_date", nullable = false, updatable = false)
32
+ private Instant createdDate = Instant.now();
33
+
34
+ @LastModifiedBy
35
+ @Column(name = "last_modified_by", length = 50)
36
+ private String lastModifiedBy;
37
+
38
+ @LastModifiedDate
39
+ @Column(name = "last_modified_date")
40
+ private Instant lastModifiedDate = Instant.now();
41
+ }
src/main/java/com/dalab/adminservice/model/CloudConnectionEntity.java ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.model;
2
+
3
+ import com.dalab.adminservice.model.enums.CloudProviderType;
4
+ import io.hypersistence.utils.hibernate.type.json.JsonType;
5
+ import jakarta.persistence.*;
6
+ import lombok.Getter;
7
+ import lombok.Setter;
8
+ import org.hibernate.annotations.GenericGenerator;
9
+ import org.hibernate.annotations.Type;
10
+
11
+ import java.time.LocalDateTime;
12
+ import java.util.Map;
13
+
14
+ @Getter
15
+ @Setter
16
+ @Entity
17
+ @Table(name = "dalab_cloud_connection")
18
+ public class CloudConnectionEntity extends AbstractAuditableEntity {
19
+
20
+ @Id
21
+ @GeneratedValue(generator = "uuid2")
22
+ @GenericGenerator(name = "uuid2", strategy = "uuid2")
23
+ @Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
24
+ private String id;
25
+
26
+ @Column(name = "name", nullable = false, length = 100)
27
+ private String name;
28
+
29
+ @Column(name = "description", length = 500)
30
+ private String description;
31
+
32
+ @Enumerated(EnumType.STRING)
33
+ @Column(name = "provider_type", nullable = false, length = 50)
34
+ private CloudProviderType providerType;
35
+
36
+ @Type(JsonType.class)
37
+ @Column(name = "connection_parameters", columnDefinition = "jsonb")
38
+ private Map<String, String> connectionParameters;
39
+
40
+ @Lob // Or @Column(length = large_enough_value) depending on DB and encrypted size
41
+ @Column(name = "encrypted_credentials", nullable = true) // Storing as byte[] might be better for encrypted data
42
+ private String encryptedCredentials; // Placeholder: In a real app, this would be byte[] and encrypted
43
+
44
+ @Column(name = "enabled", nullable = false)
45
+ private boolean enabled = true;
46
+
47
+ @Column(name = "last_connection_test_at")
48
+ private LocalDateTime lastConnectionTestAt;
49
+
50
+ @Column(name = "last_connection_test_status", length = 50)
51
+ private String lastConnectionTestStatus; // e.g., SUCCESS, FAILED
52
+ }
src/main/java/com/dalab/adminservice/model/ServiceConfigEntity.java ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.model;
2
+
3
+ import jakarta.persistence.Column;
4
+ import jakarta.persistence.Entity;
5
+ import jakarta.persistence.Id;
6
+ import jakarta.persistence.Table;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+ import lombok.Data;
10
+ import lombok.NoArgsConstructor;
11
+
12
+ /**
13
+ * Entity for service configuration management.
14
+ */
15
+ @Entity
16
+ @Table(name = "service_configs")
17
+ @Data
18
+ @Builder
19
+ @NoArgsConstructor
20
+ @AllArgsConstructor
21
+ public class ServiceConfigEntity {
22
+
23
+ @Id
24
+ @Column(nullable = false, unique = true, length = 100)
25
+ private String serviceId;
26
+
27
+ @Column(length = 255)
28
+ private String displayName;
29
+
30
+ @Column(length = 1000)
31
+ private String description;
32
+
33
+ @Column(length = 50)
34
+ private String version;
35
+
36
+ @Column(length = 500)
37
+ private String endpoint;
38
+
39
+ @Builder.Default
40
+ private boolean enabled = true;
41
+
42
+ }
src/main/java/com/dalab/adminservice/model/enums/CloudProviderType.java ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.model.enums;
2
+
3
+ /**
4
+ * Enumeration of supported cloud provider types.
5
+ */
6
+ public enum CloudProviderType {
7
+ AWS,
8
+ AZURE,
9
+ GCP,
10
+ OTHER
11
+ }
src/main/java/com/dalab/adminservice/repository/CloudConnectionRepository.java ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.repository;
2
+
3
+ import com.dalab.adminservice.model.CloudConnectionEntity;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ import java.util.Optional;
8
+
9
+ @Repository
10
+ public interface CloudConnectionRepository extends JpaRepository<CloudConnectionEntity, String> {
11
+ Optional<CloudConnectionEntity> findByName(String name);
12
+ }
src/main/java/com/dalab/adminservice/repository/ServiceConfigRepository.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.repository;
2
+
3
+ import java.util.Optional;
4
+ import java.util.UUID;
5
+
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.stereotype.Repository;
8
+
9
+ import com.dalab.adminservice.model.ServiceConfigEntity;
10
+
11
+ /**
12
+ * Repository interface for ServiceConfig entities
13
+ */
14
+ @Repository
15
+ public interface ServiceConfigRepository extends JpaRepository<ServiceConfigEntity, UUID> {
16
+
17
+ /**
18
+ * Find service configuration by service ID
19
+ * @param serviceId the service identifier
20
+ * @return Optional containing the service config if found
21
+ */
22
+ Optional<ServiceConfigEntity> findByServiceId(String serviceId);
23
+ }
src/main/java/com/dalab/adminservice/service/ICloudConnectionService.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ import com.dalab.adminservice.dto.CloudConnectionDTO;
4
+ import com.dalab.adminservice.dto.CloudConnectionTestResultDTO;
5
+
6
+ import java.util.List;
7
+ import java.util.Optional;
8
+
9
+ public interface ICloudConnectionService {
10
+ List<CloudConnectionDTO> getAllCloudConnections();
11
+ Optional<CloudConnectionDTO> getCloudConnectionById(String id);
12
+ CloudConnectionDTO createCloudConnection(CloudConnectionDTO cloudConnectionDTO);
13
+ CloudConnectionDTO updateCloudConnection(String id, CloudConnectionDTO cloudConnectionDTO);
14
+ void deleteCloudConnection(String id);
15
+ CloudConnectionTestResultDTO testCloudConnection(String id);
16
+ // Method to get decrypted credentials - internal use, not exposed via API directly
17
+ String getDecryptedCredentials(String id);
18
+ }
src/main/java/com/dalab/adminservice/service/IEncryptionService.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ /**
4
+ * Service interface for encryption and decryption operations.
5
+ */
6
+ public interface IEncryptionService {
7
+
8
+ /**
9
+ * Encrypt the given data.
10
+ * @param data The data to encrypt
11
+ * @return The encrypted data as a string
12
+ */
13
+ String encrypt(String data);
14
+
15
+ /**
16
+ * Decrypt the given encrypted data.
17
+ * @param encryptedData The encrypted data to decrypt
18
+ * @return The decrypted data as a string
19
+ */
20
+ String decrypt(String encryptedData);
21
+
22
+ }
src/main/java/com/dalab/adminservice/service/IJobStatusService.java ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ import com.dalab.adminservice.dto.AggregatedJobStatusDTO;
4
+
5
+ /**
6
+ * Service interface for job status aggregation operations.
7
+ */
8
+ public interface IJobStatusService {
9
+
10
+ /**
11
+ * Get aggregated job statuses from all microservices.
12
+ * @return Aggregated job status DTO
13
+ */
14
+ AggregatedJobStatusDTO getAggregatedJobStatuses();
15
+
16
+ }
src/main/java/com/dalab/adminservice/service/IRoleService.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ import java.util.List;
4
+
5
+ import com.dalab.adminservice.dto.RoleDTO;
6
+
7
+ /**
8
+ * Service interface for role management operations.
9
+ */
10
+ public interface IRoleService {
11
+
12
+ /**
13
+ * Get all available realm roles.
14
+ * @return List of role DTOs
15
+ */
16
+ List<RoleDTO> getAllRealmRoles();
17
+
18
+ }
src/main/java/com/dalab/adminservice/service/IServiceConfigService.java ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ import com.dalab.adminservice.dto.ServiceConfigDTO;
4
+
5
+ import java.util.List;
6
+ import java.util.Optional;
7
+
8
+ public interface IServiceConfigService {
9
+ List<ServiceConfigDTO> getAllServiceConfigs();
10
+ Optional<ServiceConfigDTO> getServiceConfigById(String serviceId);
11
+ ServiceConfigDTO createServiceConfig(ServiceConfigDTO serviceConfigDTO);
12
+ ServiceConfigDTO updateServiceConfig(String serviceId, ServiceConfigDTO serviceConfigDTO);
13
+ // No delete operation defined for now, services are typically not "deleted" but maybe disabled.
14
+ }
src/main/java/com/dalab/adminservice/service/IUserService.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service;
2
+
3
+ import com.dalab.adminservice.dto.UserDTO;
4
+ import org.keycloak.representations.idm.RoleRepresentation;
5
+
6
+ import java.util.List;
7
+ import java.util.Optional;
8
+
9
+ public interface IUserService {
10
+ List<UserDTO> getAllUsers(Integer firstResult, Integer maxResults);
11
+ Optional<UserDTO> getUserById(String userId);
12
+ Optional<UserDTO> getUserByUsername(String username);
13
+ UserDTO createUser(UserDTO userDTO);
14
+ UserDTO updateUser(String userId, UserDTO userDTO);
15
+ void deleteUser(String userId);
16
+ void assignRealmRolesToUser(String userId, List<String> roleNames);
17
+ List<RoleRepresentation> getAvailableRealmRoles(); // Helper to get roles for UI
18
+ List<RoleRepresentation> getUserRealmRoles(String userId);
19
+ }
src/main/java/com/dalab/adminservice/service/impl/BasicEncryptionServiceImpl.java ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service.impl;
2
+
3
+ import com.dalab.adminservice.service.IEncryptionService;
4
+ import lombok.extern.slf4j.Slf4j;
5
+ import org.springframework.beans.factory.annotation.Value;
6
+ import org.springframework.stereotype.Service;
7
+ import javax.crypto.Cipher;
8
+ import javax.crypto.spec.SecretKeySpec;
9
+ import java.nio.charset.StandardCharsets;
10
+ import java.util.Base64;
11
+
12
+ @Service
13
+ @Slf4j
14
+ public class BasicEncryptionServiceImpl implements IEncryptionService {
15
+
16
+ private static final String ALGORITHM = "AES";
17
+ private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; // ECB is simple but less secure for some uses.
18
+
19
+ @Value("${app.encryption.key:DefaultTestEncrKey}") // 16, 24, or 32 bytes for AES-128, AES-192, or AES-256. IMPORTANT: Store securely, not hardcoded/defaulted in prod.
20
+ private String encryptionKeyString;
21
+
22
+ private SecretKeySpec getSecretKeySpec() {
23
+ // Ensure the key is the correct length (e.g., 16 bytes for AES-128)
24
+ byte[] keyBytes = new byte[16];
25
+ byte[] providedKeyBytes = encryptionKeyString.getBytes(StandardCharsets.UTF_8);
26
+ System.arraycopy(providedKeyBytes, 0, keyBytes, 0, Math.min(providedKeyBytes.length, keyBytes.length));
27
+ return new SecretKeySpec(keyBytes, ALGORITHM);
28
+ }
29
+
30
+ @Override
31
+ public String encrypt(String data) {
32
+ if (data == null) return null;
33
+ try {
34
+ Cipher cipher = Cipher.getInstance(TRANSFORMATION);
35
+ cipher.init(Cipher.ENCRYPT_MODE, getSecretKeySpec());
36
+ byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
37
+ return Base64.getEncoder().encodeToString(encryptedBytes);
38
+ } catch (Exception e) {
39
+ log.error("Error encrypting data", e);
40
+ // In a real app, throw a specific EncryptionException
41
+ throw new RuntimeException("Error encrypting data", e);
42
+ }
43
+ }
44
+
45
+ @Override
46
+ public String decrypt(String encryptedData) {
47
+ if (encryptedData == null) return null;
48
+ try {
49
+ Cipher cipher = Cipher.getInstance(TRANSFORMATION);
50
+ cipher.init(Cipher.DECRYPT_MODE, getSecretKeySpec());
51
+ byte[] originalBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
52
+ return new String(originalBytes, StandardCharsets.UTF_8);
53
+ } catch (Exception e) {
54
+ log.error("Error decrypting data", e);
55
+ // In a real app, throw a specific EncryptionException or return null/empty based on policy
56
+ throw new RuntimeException("Error decrypting data", e);
57
+ }
58
+ }
59
+ }
src/main/java/com/dalab/adminservice/service/impl/CloudConnectionServiceImpl.java ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service.impl;
2
+
3
+ import com.dalab.adminservice.dto.CloudConnectionDTO;
4
+ import com.dalab.adminservice.dto.CloudConnectionTestResultDTO;
5
+ import com.dalab.adminservice.exception.ConflictException;
6
+ import com.dalab.adminservice.exception.NotFoundException;
7
+ import com.dalab.adminservice.mapper.CloudConnectionMapper;
8
+ import com.dalab.adminservice.model.CloudConnectionEntity;
9
+ import com.dalab.adminservice.repository.CloudConnectionRepository;
10
+ import com.dalab.adminservice.service.ICloudConnectionService;
11
+ import com.dalab.adminservice.service.IEncryptionService;
12
+ import lombok.RequiredArgsConstructor;
13
+ import lombok.extern.slf4j.Slf4j;
14
+ import org.springframework.stereotype.Service;
15
+ import org.springframework.transaction.annotation.Transactional;
16
+ import org.springframework.util.StringUtils;
17
+
18
+ import java.time.LocalDateTime;
19
+ import java.time.format.DateTimeFormatter;
20
+ import java.util.List;
21
+ import java.util.Optional;
22
+
23
+ @Service
24
+ @RequiredArgsConstructor
25
+ @Slf4j
26
+ public class CloudConnectionServiceImpl implements ICloudConnectionService {
27
+
28
+ private final CloudConnectionRepository cloudConnectionRepository;
29
+ private final CloudConnectionMapper cloudConnectionMapper;
30
+ private final IEncryptionService encryptionService; // For encrypting/decrypting sensitiveCredentials
31
+
32
+ @Override
33
+ @Transactional(readOnly = true)
34
+ public List<CloudConnectionDTO> getAllCloudConnections() {
35
+ log.debug("Fetching all cloud connections");
36
+ return cloudConnectionMapper.toDtoList(cloudConnectionRepository.findAll());
37
+ }
38
+
39
+ @Override
40
+ @Transactional(readOnly = true)
41
+ public Optional<CloudConnectionDTO> getCloudConnectionById(String id) {
42
+ log.debug("Fetching cloud connection by ID: {}", id);
43
+ return cloudConnectionRepository.findById(id)
44
+ .map(cloudConnectionMapper::toDto);
45
+ }
46
+
47
+ @Override
48
+ @Transactional
49
+ public CloudConnectionDTO createCloudConnection(CloudConnectionDTO cloudConnectionDTO) {
50
+ log.info("Creating new cloud connection with name: {}", cloudConnectionDTO.getName());
51
+ cloudConnectionRepository.findByName(cloudConnectionDTO.getName()).ifPresent(entity -> {
52
+ throw new ConflictException("Cloud connection with name '" + cloudConnectionDTO.getName() + "' already exists.");
53
+ });
54
+
55
+ CloudConnectionEntity entity = cloudConnectionMapper.toEntity(cloudConnectionDTO);
56
+ if (StringUtils.hasText(cloudConnectionDTO.getSensitiveCredentials())) {
57
+ entity.setEncryptedCredentials(encryptionService.encrypt(cloudConnectionDTO.getSensitiveCredentials()));
58
+ }
59
+ CloudConnectionEntity savedEntity = cloudConnectionRepository.save(entity);
60
+ return cloudConnectionMapper.toDto(savedEntity);
61
+ }
62
+
63
+ @Override
64
+ @Transactional
65
+ public CloudConnectionDTO updateCloudConnection(String id, CloudConnectionDTO cloudConnectionDTO) {
66
+ log.info("Updating cloud connection with ID: {}", id);
67
+ CloudConnectionEntity existingEntity = cloudConnectionRepository.findById(id)
68
+ .orElseThrow(() -> new NotFoundException("Cloud connection with ID '" + id + "' not found."));
69
+
70
+ // Check if name is being changed and if the new name already exists for another entity
71
+ if (StringUtils.hasText(cloudConnectionDTO.getName()) && !existingEntity.getName().equals(cloudConnectionDTO.getName())) {
72
+ cloudConnectionRepository.findByName(cloudConnectionDTO.getName()).ifPresent(entity -> {
73
+ if (!entity.getId().equals(id)) { // If it's not the same entity
74
+ throw new ConflictException("Cloud connection with name '" + cloudConnectionDTO.getName() + "' already exists.");
75
+ }
76
+ });
77
+ }
78
+
79
+ cloudConnectionMapper.updateEntityFromDto(cloudConnectionDTO, existingEntity);
80
+ if (StringUtils.hasText(cloudConnectionDTO.getSensitiveCredentials())) {
81
+ log.debug("Updating encrypted credentials for cloud connection ID: {}", id);
82
+ existingEntity.setEncryptedCredentials(encryptionService.encrypt(cloudConnectionDTO.getSensitiveCredentials()));
83
+ } else {
84
+ // If sensitiveCredentials is null or empty in DTO, it means user doesn't want to update them.
85
+ // If user wants to clear them, an explicit mechanism should be used.
86
+ // For now, we just don't update if it's not provided.
87
+ }
88
+
89
+ CloudConnectionEntity updatedEntity = cloudConnectionRepository.save(existingEntity);
90
+ return cloudConnectionMapper.toDto(updatedEntity);
91
+ }
92
+
93
+ @Override
94
+ @Transactional
95
+ public void deleteCloudConnection(String id) {
96
+ log.info("Deleting cloud connection with ID: {}", id);
97
+ if (!cloudConnectionRepository.existsById(id)) {
98
+ throw new NotFoundException("Cloud connection with ID '" + id + "' not found.");
99
+ }
100
+ cloudConnectionRepository.deleteById(id);
101
+ }
102
+
103
+ @Override
104
+ @Transactional
105
+ public CloudConnectionTestResultDTO testCloudConnection(String id) {
106
+ log.info("Testing cloud connection with ID: {}", id);
107
+ CloudConnectionEntity entity = cloudConnectionRepository.findById(id)
108
+ .orElseThrow(() -> new NotFoundException("Cloud connection with ID '" + id + "' not found for testing."));
109
+
110
+ // Placeholder for actual connection testing logic
111
+ // This would involve:
112
+ // 1. Decrypting entity.getEncryptedCredentials() using encryptionService.decrypt()
113
+ // 2. Using the decrypted credentials and entity.getConnectionParameters()
114
+ // to attempt a connection to the specified cloudProviderType (e.g., using GCP/AWS SDKs).
115
+ boolean testSuccess = false;
116
+ String message = "Test not implemented for provider: " + entity.getProviderType();
117
+
118
+ // Simulate a test
119
+ try {
120
+ String decryptedCreds = encryptionService.decrypt(entity.getEncryptedCredentials());
121
+ if (decryptedCreds != null && !decryptedCreds.isEmpty()) {
122
+ // Simulate success if credentials exist for the sake of example
123
+ message = "Simulated connection test successful for " + entity.getName();
124
+ testSuccess = true;
125
+ log.info("Simulated connection test successful for ID: {}. Decrypted (sample): {}", id, decryptedCreds.substring(0, Math.min(decryptedCreds.length(), 10)));
126
+ } else {
127
+ message = "Simulated connection test failed: No credentials to test for " + entity.getName();
128
+ log.warn("Simulated connection test failed for ID: {}. No credentials found.", id);
129
+ }
130
+ } catch (Exception e) {
131
+ log.error("Error during simulated connection test for ID: {}", id, e);
132
+ message = "Simulated connection test failed: " + e.getMessage();
133
+ }
134
+
135
+ entity.setLastConnectionTestAt(LocalDateTime.now());
136
+ entity.setLastConnectionTestStatus(testSuccess ? "SUCCESS" : "FAILED");
137
+ cloudConnectionRepository.save(entity);
138
+
139
+ return CloudConnectionTestResultDTO.builder()
140
+ .success(testSuccess)
141
+ .message(message)
142
+ .testedAt(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
143
+ .build();
144
+ }
145
+
146
+ @Override
147
+ @Transactional(readOnly = true)
148
+ public String getDecryptedCredentials(String id) {
149
+ log.debug("Retrieving decrypted credentials for cloud connection ID: {} (internal use)", id);
150
+ CloudConnectionEntity entity = cloudConnectionRepository.findById(id)
151
+ .orElseThrow(() -> new NotFoundException("Cloud connection with ID '" + id + "' not found."));
152
+
153
+ if (!StringUtils.hasText(entity.getEncryptedCredentials())) {
154
+ log.warn("No encrypted credentials found for cloud connection ID: {}", id);
155
+ return null;
156
+ }
157
+ try {
158
+ return encryptionService.decrypt(entity.getEncryptedCredentials());
159
+ } catch (Exception e) {
160
+ log.error("Failed to decrypt credentials for cloud connection ID: {}. Error: {}", id, e.getMessage());
161
+ // Depending on policy, you might re-throw, or return null, or a specific error marker.
162
+ // Throwing an exception is safer to indicate failure clearly.
163
+ throw new RuntimeException("Failed to decrypt credentials for ID: " + id, e);
164
+ }
165
+ }
166
+ }
src/main/java/com/dalab/adminservice/service/impl/JobStatusServiceImpl.java ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service.impl;
2
+
3
+ import java.util.ArrayList;
4
+ import java.util.List;
5
+
6
+ import org.springframework.stereotype.Service;
7
+
8
+ import com.dalab.adminservice.client.IAutoarchivalTaskApiClient;
9
+ import com.dalab.adminservice.client.IAutocomplianceJobApiClient;
10
+ import com.dalab.adminservice.client.IAutodeleteTaskApiClient;
11
+ import com.dalab.adminservice.client.IAutolabelJobApiClient;
12
+ import com.dalab.adminservice.client.IDiscoveryJobApiClient;
13
+ import com.dalab.adminservice.dto.AggregatedJobStatusDTO;
14
+ import com.dalab.adminservice.dto.JobStatusDTO;
15
+ import com.dalab.adminservice.service.IJobStatusService;
16
+
17
+ import lombok.RequiredArgsConstructor;
18
+ import lombok.extern.slf4j.Slf4j;
19
+
20
+ /**
21
+ * Service implementation for aggregating job statuses from all DALab microservices
22
+ */
23
+ @Service
24
+ @RequiredArgsConstructor
25
+ @Slf4j
26
+ public class JobStatusServiceImpl implements IJobStatusService {
27
+
28
+ private final IDiscoveryJobApiClient discoveryJobApiClient;
29
+ private final IAutolabelJobApiClient autolabelJobApiClient;
30
+ private final IAutoarchivalTaskApiClient autoarchivalTaskApiClient;
31
+ private final IAutodeleteTaskApiClient autodeleteTaskApiClient;
32
+ private final IAutocomplianceJobApiClient autocomplianceJobApiClient;
33
+
34
+ @Override
35
+ public AggregatedJobStatusDTO getAggregatedJobStatuses() {
36
+ log.debug("Aggregating job statuses from all DALab services");
37
+
38
+ List<JobStatusDTO> allJobs = new ArrayList<>();
39
+
40
+ // Aggregate jobs from all services with error handling
41
+ aggregateFromService("Discovery", () -> discoveryJobApiClient.getDiscoveryJobs(), allJobs);
42
+ aggregateFromService("Autolabel", () -> autolabelJobApiClient.getAutolabelJobs(), allJobs);
43
+ aggregateFromService("Autoarchival", () -> autoarchivalTaskApiClient.getArchivalTasks(), allJobs);
44
+ aggregateFromService("Autodelete", () -> autodeleteTaskApiClient.getDeletionTasks(), allJobs);
45
+ aggregateFromService("Autocompliance", () -> autocomplianceJobApiClient.getComplianceReportJobs(), allJobs);
46
+
47
+ return buildAggregatedResult(allJobs);
48
+ }
49
+
50
+ private void aggregateFromService(String serviceName, ServiceJobProvider provider, List<JobStatusDTO> allJobs) {
51
+ try {
52
+ List<JobStatusDTO> serviceJobs = provider.getJobs();
53
+ if (serviceJobs != null) {
54
+ allJobs.addAll(serviceJobs);
55
+ log.debug("Aggregated {} jobs from {} service", serviceJobs.size(), serviceName);
56
+ }
57
+ } catch (Exception e) {
58
+ log.warn("Failed to retrieve jobs from {} service: {}", serviceName, e.getMessage());
59
+ }
60
+ }
61
+
62
+ private AggregatedJobStatusDTO buildAggregatedResult(List<JobStatusDTO> allJobs) {
63
+ int totalJobs = allJobs.size();
64
+ int pendingJobs = 0;
65
+ int runningJobs = 0;
66
+ int completedSuccessJobs = 0;
67
+ int completedFailedJobs = 0;
68
+
69
+ for (JobStatusDTO job : allJobs) {
70
+ String status = job.getStatus();
71
+ if (status == null) continue;
72
+
73
+ switch (status.toUpperCase()) {
74
+ case "PENDING":
75
+ pendingJobs++;
76
+ break;
77
+ case "RUNNING":
78
+ case "IN_PROGRESS":
79
+ runningJobs++;
80
+ break;
81
+ case "COMPLETED_SUCCESS":
82
+ case "COMPLETED":
83
+ case "SUCCESS":
84
+ completedSuccessJobs++;
85
+ break;
86
+ case "COMPLETED_FAILED":
87
+ case "FAILED":
88
+ case "ERROR":
89
+ completedFailedJobs++;
90
+ break;
91
+ }
92
+ }
93
+
94
+ return AggregatedJobStatusDTO.builder()
95
+ .totalJobs(totalJobs)
96
+ .pendingJobs(pendingJobs)
97
+ .runningJobs(runningJobs)
98
+ .completedSuccessJobs(completedSuccessJobs)
99
+ .completedFailedJobs(completedFailedJobs)
100
+ .jobs(allJobs)
101
+ .build();
102
+ }
103
+
104
+ @FunctionalInterface
105
+ private interface ServiceJobProvider {
106
+ List<JobStatusDTO> getJobs();
107
+ }
108
+ }
src/main/java/com/dalab/adminservice/service/impl/RoleServiceImpl.java ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.adminservice.service.impl;
2
+
3
+ import com.dalab.adminservice.dto.RoleDTO;
4
+ import com.dalab.adminservice.exception.KeycloakAdminException;
5
+ import com.dalab.adminservice.mapper.RoleMapper;
6
+ import com.dalab.adminservice.service.IRoleService;
7
+ import jakarta.ws.rs.WebApplicationException;
8
+ import lombok.RequiredArgsConstructor;
9
+ import lombok.extern.slf4j.Slf4j;
10
+ import org.keycloak.admin.client.Keycloak;
11
+ import org.keycloak.admin.client.resource.RealmResource;
12
+ import org.keycloak.representations.idm.RoleRepresentation;
13
+ import org.springframework.beans.factory.annotation.Value;
14
+ import org.springframework.stereotype.Service;
15
+
16
+ import java.util.List;
17
+
18
+ @Service
19
+ @RequiredArgsConstructor
20
+ @Slf4j
21
+ public class RoleServiceImpl implements IRoleService {
22
+
23
+ private final Keycloak keycloakAdminClient;
24
+ private final RoleMapper roleMapper;
25
+
26
+ @Value("${keycloak.realm}")
27
+ private String realmName;
28
+
29
+ private RealmResource getRealmResource() {
30
+ return keycloakAdminClient.realm(realmName);
31
+ }
32
+
33
+ @Override
34
+ public List<RoleDTO> getAllRealmRoles() {
35
+ log.debug("Fetching all available realm roles from Keycloak realm: {}", realmName);
36
+ try {
37
+ List<RoleRepresentation> roleRepresentations = getRealmResource().roles().list();
38
+ return roleMapper.toDtoList(roleRepresentations);
39
+ } catch (WebApplicationException e) {
40
+ log.error("Keycloak admin error fetching available realm roles: {}", e.getMessage(), e);
41
+ throw new KeycloakAdminException("Error fetching available realm roles: " + e.getMessage(), e);
42
+ }
43
+ }
44
+ }