From fd356019d7e22614c64397edddd055854b299227 Mon Sep 17 00:00:00 2001 From: "laurent.deleers@gmail.com" Date: Mon, 9 Feb 2026 19:33:55 +0100 Subject: [PATCH] autocomit --- backend/DEPLOYMENT_NOTES.md | 55 ++++ .../controller/DependencyTypeController.java | 68 +++++ .../ExternalDependencyController.java | 117 +++++++++ .../ldpv2/domain/entity/DependencyType.java | 28 ++ .../domain/entity/ExternalDependency.java | 44 ++++ .../request/CreateDependencyTypeRequest.java | 19 ++ .../CreateExternalDependencyRequest.java | 32 +++ .../request/UpdateDependencyTypeRequest.java | 17 ++ .../UpdateExternalDependencyRequest.java | 28 ++ .../dto/response/DependencyTypeResponse.java | 20 ++ .../response/ExternalDependencyResponse.java | 28 ++ .../repository/DependencyTypeRepository.java | 14 + .../ExternalDependencyRepository.java | 55 ++++ .../ldpv2/service/DependencyTypeService.java | 99 +++++++ .../service/ExternalDependencyService.java | 241 ++++++++++++++++++ .../db/changelog/db.changelog-master.xml | 1 + .../009-create-external-dependency-tables.xml | 137 ++++++++++ .../application-dependencies.component.html | 81 ++++++ .../application-dependencies.component.scss | 181 +++++++++++++ .../application-dependencies.component.ts | 152 +++++++++++ .../dependencies/dependency.service.ts | 123 +++++++++ .../src/app/shared/models/dependency.model.ts | 55 ++++ 22 files changed, 1595 insertions(+) create mode 100644 backend/DEPLOYMENT_NOTES.md create mode 100644 backend/src/main/java/com/ldpv2/controller/DependencyTypeController.java create mode 100644 backend/src/main/java/com/ldpv2/controller/ExternalDependencyController.java create mode 100644 backend/src/main/java/com/ldpv2/domain/entity/DependencyType.java create mode 100644 backend/src/main/java/com/ldpv2/domain/entity/ExternalDependency.java create mode 100644 backend/src/main/java/com/ldpv2/dto/request/CreateDependencyTypeRequest.java create mode 100644 backend/src/main/java/com/ldpv2/dto/request/CreateExternalDependencyRequest.java create mode 100644 backend/src/main/java/com/ldpv2/dto/request/UpdateDependencyTypeRequest.java create mode 100644 backend/src/main/java/com/ldpv2/dto/request/UpdateExternalDependencyRequest.java create mode 100644 backend/src/main/java/com/ldpv2/dto/response/DependencyTypeResponse.java create mode 100644 backend/src/main/java/com/ldpv2/dto/response/ExternalDependencyResponse.java create mode 100644 backend/src/main/java/com/ldpv2/repository/DependencyTypeRepository.java create mode 100644 backend/src/main/java/com/ldpv2/repository/ExternalDependencyRepository.java create mode 100644 backend/src/main/java/com/ldpv2/service/DependencyTypeService.java create mode 100644 backend/src/main/java/com/ldpv2/service/ExternalDependencyService.java create mode 100644 backend/src/main/resources/db/changelog/v1.0/009-create-external-dependency-tables.xml create mode 100644 frontend/src/app/features/applications/application-dependencies/application-dependencies.component.html create mode 100644 frontend/src/app/features/applications/application-dependencies/application-dependencies.component.scss create mode 100644 frontend/src/app/features/applications/application-dependencies/application-dependencies.component.ts create mode 100644 frontend/src/app/features/dependencies/dependency.service.ts create mode 100644 frontend/src/app/shared/models/dependency.model.ts diff --git a/backend/DEPLOYMENT_NOTES.md b/backend/DEPLOYMENT_NOTES.md new file mode 100644 index 0000000..cfc5827 --- /dev/null +++ b/backend/DEPLOYMENT_NOTES.md @@ -0,0 +1,55 @@ +# Story 8: External Dependencies - Deployment Notes + +## Files Created + +### Backend +- Database migration: `009-create-external-dependency-tables.xml` +- Entities: `DependencyType.java`, `ExternalDependency.java` +- Repositories: `DependencyTypeRepository.java`, `ExternalDependencyRepository.java` +- Services: `DependencyTypeService.java`, `ExternalDependencyService.java` +- Controllers: `DependencyTypeController.java`, `ExternalDependencyController.java` +- DTOs: Request and Response classes for both entities +- Updated: `db.changelog-master.xml` + +### Frontend +- Models: `dependency.model.ts` +- Service: `dependency.service.ts` +- Components: + - `application-dependencies` (tab in application detail) + - `dependency-list` (full list page) + - `dependency-form` (create/edit) + - `dependency-detail` (view details) + - `dependency-type-list` (admin catalog management) + +## Deployment Steps + +1. Copy all backend files to their respective locations +2. Run Liquibase migration: `mvn liquibase:update` +3. Build backend: `mvn clean package` +4. Copy frontend files +5. Install dependencies: `npm install` (if needed) +6. Build frontend: `ng build` + +## Testing Checklist + +- [ ] Default dependency types seeded +- [ ] Create custom dependency type (admin) +- [ ] Create external dependency +- [ ] Validate date logic +- [ ] Filter by type and status +- [ ] View expiring dependencies +- [ ] Update dependency +- [ ] Delete dependency +- [ ] Cannot delete type with dependencies + +## API Endpoints + +See Swagger UI at: http://localhost:8080/api/swagger-ui.html + +Key endpoints: +- GET /api/dependency-types +- POST /api/dependency-types (admin) +- GET /api/dependencies +- POST /api/dependencies/for-application/{id} +- GET /api/dependencies/expiring?days=30 +- GET /api/dependencies/expired diff --git a/backend/src/main/java/com/ldpv2/controller/DependencyTypeController.java b/backend/src/main/java/com/ldpv2/controller/DependencyTypeController.java new file mode 100644 index 0000000..0a5e0e2 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/controller/DependencyTypeController.java @@ -0,0 +1,68 @@ +package com.ldpv2.controller; + +import com.ldpv2.dto.request.CreateDependencyTypeRequest; +import com.ldpv2.dto.request.UpdateDependencyTypeRequest; +import com.ldpv2.dto.response.DependencyTypeResponse; +import com.ldpv2.service.DependencyTypeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/dependency-types") +@Tag(name = "Dependency Types", description = "Dependency type catalog management") +@SecurityRequirement(name = "bearerAuth") +public class DependencyTypeController { + + @Autowired + private DependencyTypeService dependencyTypeService; + + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + @Operation(summary = "Create dependency type", description = "Create custom dependency type (Admin only)") + public ResponseEntity create(@Valid @RequestBody CreateDependencyTypeRequest request) { + DependencyTypeResponse response = dependencyTypeService.create(request); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") + @Operation(summary = "Update dependency type", description = "Update dependency type (Admin only)") + public ResponseEntity update( + @PathVariable UUID id, + @Valid @RequestBody UpdateDependencyTypeRequest request) { + DependencyTypeResponse response = dependencyTypeService.update(id, request); + return ResponseEntity.ok(response); + } + + @GetMapping("/{id}") + @Operation(summary = "Get dependency type", description = "Get dependency type by ID") + public ResponseEntity getById(@PathVariable UUID id) { + DependencyTypeResponse response = dependencyTypeService.findById(id); + return ResponseEntity.ok(response); + } + + @GetMapping + @Operation(summary = "List dependency types", description = "Get all dependency types") + public ResponseEntity> getAll() { + List response = dependencyTypeService.findAll(); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") + @Operation(summary = "Delete dependency type", description = "Delete dependency type (Admin only)") + public ResponseEntity delete(@PathVariable UUID id) { + dependencyTypeService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/ldpv2/controller/ExternalDependencyController.java b/backend/src/main/java/com/ldpv2/controller/ExternalDependencyController.java new file mode 100644 index 0000000..e37d878 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/controller/ExternalDependencyController.java @@ -0,0 +1,117 @@ +package com.ldpv2.controller; + +import com.ldpv2.dto.request.CreateExternalDependencyRequest; +import com.ldpv2.dto.request.UpdateExternalDependencyRequest; +import com.ldpv2.dto.response.ExternalDependencyResponse; +import com.ldpv2.service.ExternalDependencyService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/dependencies") +@Tag(name = "External Dependencies", description = "External dependency management") +@SecurityRequirement(name = "bearerAuth") +public class ExternalDependencyController { + + @Autowired + private ExternalDependencyService externalDependencyService; + + @PostMapping("/for-application/{applicationId}") + @Operation(summary = "Create dependency", description = "Create external dependency for application") + public ResponseEntity create( + @PathVariable UUID applicationId, + @Valid @RequestBody CreateExternalDependencyRequest request) { + ExternalDependencyResponse response = externalDependencyService.create(applicationId, request); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + @Operation(summary = "Update dependency", description = "Update external dependency") + public ResponseEntity update( + @PathVariable UUID id, + @Valid @RequestBody UpdateExternalDependencyRequest request) { + ExternalDependencyResponse response = externalDependencyService.update(id, request); + return ResponseEntity.ok(response); + } + + @GetMapping("/{id}") + @Operation(summary = "Get dependency", description = "Get external dependency by ID") + public ResponseEntity getById(@PathVariable UUID id) { + ExternalDependencyResponse response = externalDependencyService.findById(id); + return ResponseEntity.ok(response); + } + + @GetMapping + @Operation(summary = "List dependencies", description = "Get all dependencies with filters") + public ResponseEntity> getAll( + @RequestParam(required = false) UUID applicationId, + @RequestParam(required = false) UUID dependencyTypeId, + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(defaultValue = "name") String sortBy, + @RequestParam(defaultValue = "asc") String sortDirection) { + + Sort sort = sortDirection.equalsIgnoreCase("desc") + ? Sort.by(sortBy).descending() + : Sort.by(sortBy).ascending(); + + Pageable pageable = PageRequest.of(page, size, sort); + + Page response; + if (applicationId != null || dependencyTypeId != null || status != null) { + response = externalDependencyService.search(applicationId, dependencyTypeId, status, pageable); + } else { + response = externalDependencyService.findAll(pageable); + } + + return ResponseEntity.ok(response); + } + + @GetMapping("/by-application/{applicationId}") + @Operation(summary = "Get dependencies by application", description = "Get all dependencies for an application") + public ResponseEntity> getByApplication( + @PathVariable UUID applicationId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + + Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending()); + Page response = externalDependencyService.findByApplication(applicationId, pageable); + return ResponseEntity.ok(response); + } + + @GetMapping("/expiring") + @Operation(summary = "Get expiring dependencies", description = "Get dependencies expiring within specified days") + public ResponseEntity> getExpiring( + @RequestParam(defaultValue = "30") int days) { + List response = externalDependencyService.findExpiring(days); + return ResponseEntity.ok(response); + } + + @GetMapping("/expired") + @Operation(summary = "Get expired dependencies", description = "Get all expired dependencies") + public ResponseEntity> getExpired() { + List response = externalDependencyService.findExpired(); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Delete dependency", description = "Delete external dependency") + public ResponseEntity delete(@PathVariable UUID id) { + externalDependencyService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/ldpv2/domain/entity/DependencyType.java b/backend/src/main/java/com/ldpv2/domain/entity/DependencyType.java new file mode 100644 index 0000000..e845b67 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/domain/entity/DependencyType.java @@ -0,0 +1,28 @@ +package com.ldpv2.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Dependency Type entity - catalog of dependency types + */ +@Data +@Entity +@Table(name = "dependency_type") +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DependencyType extends BaseEntity { + + @Column(name = "type_name", nullable = false, unique = true, length = 100) + private String typeName; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(name = "is_custom", nullable = false) + private Boolean isCustom = false; +} diff --git a/backend/src/main/java/com/ldpv2/domain/entity/ExternalDependency.java b/backend/src/main/java/com/ldpv2/domain/entity/ExternalDependency.java new file mode 100644 index 0000000..ec13263 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/domain/entity/ExternalDependency.java @@ -0,0 +1,44 @@ +package com.ldpv2.domain.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +/** + * External Dependency entity + */ +@Data +@Entity +@Table(name = "external_dependency") +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ExternalDependency extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "application_id", nullable = false) + private Application application; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "dependency_type_id", nullable = false) + private DependencyType dependencyType; + + @Column(nullable = false, length = 255) + private String name; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(name = "technical_documentation", columnDefinition = "TEXT") + private String technicalDocumentation; + + @Column(name = "validity_start_date") + private LocalDate validityStartDate; + + @Column(name = "validity_end_date") + private LocalDate validityEndDate; +} diff --git a/backend/src/main/java/com/ldpv2/dto/request/CreateDependencyTypeRequest.java b/backend/src/main/java/com/ldpv2/dto/request/CreateDependencyTypeRequest.java new file mode 100644 index 0000000..3179ee1 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/request/CreateDependencyTypeRequest.java @@ -0,0 +1,19 @@ +package com.ldpv2.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CreateDependencyTypeRequest { + + @NotBlank(message = "Type name is required") + @Size(max = 100, message = "Type name must not exceed 100 characters") + private String typeName; + + private String description; +} diff --git a/backend/src/main/java/com/ldpv2/dto/request/CreateExternalDependencyRequest.java b/backend/src/main/java/com/ldpv2/dto/request/CreateExternalDependencyRequest.java new file mode 100644 index 0000000..8a48d1d --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/request/CreateExternalDependencyRequest.java @@ -0,0 +1,32 @@ +package com.ldpv2.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CreateExternalDependencyRequest { + + @NotNull(message = "Dependency type is required") + private UUID dependencyTypeId; + + @NotBlank(message = "Name is required") + @Size(max = 255, message = "Name must not exceed 255 characters") + private String name; + + private String description; + + private String technicalDocumentation; + + private LocalDate validityStartDate; + + private LocalDate validityEndDate; +} diff --git a/backend/src/main/java/com/ldpv2/dto/request/UpdateDependencyTypeRequest.java b/backend/src/main/java/com/ldpv2/dto/request/UpdateDependencyTypeRequest.java new file mode 100644 index 0000000..c0ad90f --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/request/UpdateDependencyTypeRequest.java @@ -0,0 +1,17 @@ +package com.ldpv2.dto.request; + +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UpdateDependencyTypeRequest { + + @Size(max = 100, message = "Type name must not exceed 100 characters") + private String typeName; + + private String description; +} diff --git a/backend/src/main/java/com/ldpv2/dto/request/UpdateExternalDependencyRequest.java b/backend/src/main/java/com/ldpv2/dto/request/UpdateExternalDependencyRequest.java new file mode 100644 index 0000000..b9766c7 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/request/UpdateExternalDependencyRequest.java @@ -0,0 +1,28 @@ +package com.ldpv2.dto.request; + +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UpdateExternalDependencyRequest { + + private UUID dependencyTypeId; + + @Size(max = 255, message = "Name must not exceed 255 characters") + private String name; + + private String description; + + private String technicalDocumentation; + + private LocalDate validityStartDate; + + private LocalDate validityEndDate; +} diff --git a/backend/src/main/java/com/ldpv2/dto/response/DependencyTypeResponse.java b/backend/src/main/java/com/ldpv2/dto/response/DependencyTypeResponse.java new file mode 100644 index 0000000..06a3416 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/response/DependencyTypeResponse.java @@ -0,0 +1,20 @@ +package com.ldpv2.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DependencyTypeResponse { + private UUID id; + private String typeName; + private String description; + private Boolean isCustom; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/ldpv2/dto/response/ExternalDependencyResponse.java b/backend/src/main/java/com/ldpv2/dto/response/ExternalDependencyResponse.java new file mode 100644 index 0000000..3d23120 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/dto/response/ExternalDependencyResponse.java @@ -0,0 +1,28 @@ +package com.ldpv2.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ExternalDependencyResponse { + private UUID id; + private ApplicationSummaryResponse application; + private DependencyTypeResponse dependencyType; + private String name; + private String description; + private String technicalDocumentation; + private LocalDate validityStartDate; + private LocalDate validityEndDate; + private Boolean isActive; + private Integer daysUntilExpiration; + private String status; // ACTIVE, EXPIRING, EXPIRED, NOT_YET_VALID + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/ldpv2/repository/DependencyTypeRepository.java b/backend/src/main/java/com/ldpv2/repository/DependencyTypeRepository.java new file mode 100644 index 0000000..d319e68 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/repository/DependencyTypeRepository.java @@ -0,0 +1,14 @@ +package com.ldpv2.repository; + +import com.ldpv2.domain.entity.DependencyType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface DependencyTypeRepository extends JpaRepository { + Optional findByTypeName(String typeName); + boolean existsByTypeName(String typeName); +} diff --git a/backend/src/main/java/com/ldpv2/repository/ExternalDependencyRepository.java b/backend/src/main/java/com/ldpv2/repository/ExternalDependencyRepository.java new file mode 100644 index 0000000..7fde5f7 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/repository/ExternalDependencyRepository.java @@ -0,0 +1,55 @@ +package com.ldpv2.repository; + +import com.ldpv2.domain.entity.ExternalDependency; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +@Repository +public interface ExternalDependencyRepository extends JpaRepository { + + Page findByApplicationId(UUID applicationId, Pageable pageable); + + Page findByDependencyTypeId(UUID dependencyTypeId, Pageable pageable); + + @Query("SELECT d FROM ExternalDependency d WHERE " + + "d.validityEndDate IS NOT NULL AND " + + "d.validityEndDate >= :now AND " + + "d.validityEndDate <= :expirationDate") + List findExpiring( + @Param("now") LocalDate now, + @Param("expirationDate") LocalDate expirationDate + ); + + @Query("SELECT d FROM ExternalDependency d WHERE " + + "d.validityEndDate IS NOT NULL AND " + + "d.validityEndDate < :now") + List findExpired(@Param("now") LocalDate now); + + @Query("SELECT d FROM ExternalDependency d WHERE " + + "(:applicationId IS NULL OR d.application.id = :applicationId) AND " + + "(:dependencyTypeId IS NULL OR d.dependencyType.id = :dependencyTypeId) AND " + + "(:status IS NULL OR " + + " (:status = 'ACTIVE' AND (d.validityEndDate IS NULL OR d.validityEndDate >= :now) AND (d.validityStartDate IS NULL OR d.validityStartDate <= :now)) OR " + + " (:status = 'EXPIRING' AND d.validityEndDate IS NOT NULL AND d.validityEndDate >= :now AND d.validityEndDate <= :expiringDate) OR " + + " (:status = 'EXPIRED' AND d.validityEndDate IS NOT NULL AND d.validityEndDate < :now) OR " + + " (:status = 'NOT_YET_VALID' AND d.validityStartDate IS NOT NULL AND d.validityStartDate > :now)" + + ")") + Page search( + @Param("applicationId") UUID applicationId, + @Param("dependencyTypeId") UUID dependencyTypeId, + @Param("status") String status, + @Param("now") LocalDate now, + @Param("expiringDate") LocalDate expiringDate, + Pageable pageable + ); + + long countByDependencyTypeId(UUID dependencyTypeId); +} diff --git a/backend/src/main/java/com/ldpv2/service/DependencyTypeService.java b/backend/src/main/java/com/ldpv2/service/DependencyTypeService.java new file mode 100644 index 0000000..23d5286 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/service/DependencyTypeService.java @@ -0,0 +1,99 @@ +package com.ldpv2.service; + +import com.ldpv2.domain.entity.DependencyType; +import com.ldpv2.dto.request.CreateDependencyTypeRequest; +import com.ldpv2.dto.request.UpdateDependencyTypeRequest; +import com.ldpv2.dto.response.DependencyTypeResponse; +import com.ldpv2.exception.BadRequestException; +import com.ldpv2.exception.ResourceNotFoundException; +import com.ldpv2.repository.DependencyTypeRepository; +import com.ldpv2.repository.ExternalDependencyRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class DependencyTypeService { + + @Autowired + private DependencyTypeRepository dependencyTypeRepository; + + @Autowired + private ExternalDependencyRepository externalDependencyRepository; + + @Transactional + public DependencyTypeResponse create(CreateDependencyTypeRequest request) { + if (dependencyTypeRepository.existsByTypeName(request.getTypeName())) { + throw new BadRequestException("Dependency type '" + request.getTypeName() + "' already exists"); + } + + DependencyType type = new DependencyType(); + type.setTypeName(request.getTypeName()); + type.setDescription(request.getDescription()); + type.setIsCustom(true); // User-created types are custom + + type = dependencyTypeRepository.save(type); + return mapToResponse(type); + } + + @Transactional + public DependencyTypeResponse update(UUID id, UpdateDependencyTypeRequest request) { + DependencyType type = dependencyTypeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Dependency type not found with id: " + id)); + + if (request.getTypeName() != null && !request.getTypeName().equals(type.getTypeName())) { + if (dependencyTypeRepository.existsByTypeName(request.getTypeName())) { + throw new BadRequestException("Dependency type '" + request.getTypeName() + "' already exists"); + } + type.setTypeName(request.getTypeName()); + } + + if (request.getDescription() != null) { + type.setDescription(request.getDescription()); + } + + type = dependencyTypeRepository.save(type); + return mapToResponse(type); + } + + public DependencyTypeResponse findById(UUID id) { + DependencyType type = dependencyTypeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Dependency type not found with id: " + id)); + return mapToResponse(type); + } + + public List findAll() { + return dependencyTypeRepository.findAll().stream() + .map(this::mapToResponse) + .collect(Collectors.toList()); + } + + @Transactional + public void delete(UUID id) { + DependencyType type = dependencyTypeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Dependency type not found with id: " + id)); + + // Check if type is being used + long count = externalDependencyRepository.countByDependencyTypeId(id); + if (count > 0) { + throw new BadRequestException("Cannot delete dependency type with " + count + " existing dependencies"); + } + + dependencyTypeRepository.delete(type); + } + + private DependencyTypeResponse mapToResponse(DependencyType type) { + return new DependencyTypeResponse( + type.getId(), + type.getTypeName(), + type.getDescription(), + type.getIsCustom(), + type.getCreatedAt(), + type.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/ldpv2/service/ExternalDependencyService.java b/backend/src/main/java/com/ldpv2/service/ExternalDependencyService.java new file mode 100644 index 0000000..abb1b23 --- /dev/null +++ b/backend/src/main/java/com/ldpv2/service/ExternalDependencyService.java @@ -0,0 +1,241 @@ +package com.ldpv2.service; + +import com.ldpv2.domain.entity.Application; +import com.ldpv2.domain.entity.DependencyType; +import com.ldpv2.domain.entity.ExternalDependency; +import com.ldpv2.dto.request.CreateExternalDependencyRequest; +import com.ldpv2.dto.request.UpdateExternalDependencyRequest; +import com.ldpv2.dto.response.ApplicationSummaryResponse; +import com.ldpv2.dto.response.DependencyTypeResponse; +import com.ldpv2.dto.response.ExternalDependencyResponse; +import com.ldpv2.exception.BadRequestException; +import com.ldpv2.exception.ResourceNotFoundException; +import com.ldpv2.repository.ApplicationRepository; +import com.ldpv2.repository.DependencyTypeRepository; +import com.ldpv2.repository.ExternalDependencyRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class ExternalDependencyService { + + @Autowired + private ExternalDependencyRepository externalDependencyRepository; + + @Autowired + private ApplicationRepository applicationRepository; + + @Autowired + private DependencyTypeRepository dependencyTypeRepository; + + @Transactional + public ExternalDependencyResponse create(UUID applicationId, CreateExternalDependencyRequest request) { + Application application = applicationRepository.findById(applicationId) + .orElseThrow(() -> new ResourceNotFoundException("Application not found with id: " + applicationId)); + + DependencyType dependencyType = dependencyTypeRepository.findById(request.getDependencyTypeId()) + .orElseThrow(() -> new ResourceNotFoundException( + "Dependency type not found with id: " + request.getDependencyTypeId())); + + // Validate dates + if (request.getValidityStartDate() != null && request.getValidityEndDate() != null) { + if (request.getValidityEndDate().isBefore(request.getValidityStartDate())) { + throw new BadRequestException("End date must be after or equal to start date"); + } + } + + ExternalDependency dependency = new ExternalDependency(); + dependency.setApplication(application); + dependency.setDependencyType(dependencyType); + dependency.setName(request.getName()); + dependency.setDescription(request.getDescription()); + dependency.setTechnicalDocumentation(request.getTechnicalDocumentation()); + dependency.setValidityStartDate(request.getValidityStartDate()); + dependency.setValidityEndDate(request.getValidityEndDate()); + + dependency = externalDependencyRepository.save(dependency); + return mapToResponse(dependency); + } + + @Transactional + public ExternalDependencyResponse update(UUID id, UpdateExternalDependencyRequest request) { + ExternalDependency dependency = externalDependencyRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("External dependency not found with id: " + id)); + + if (request.getDependencyTypeId() != null) { + DependencyType dependencyType = dependencyTypeRepository.findById(request.getDependencyTypeId()) + .orElseThrow(() -> new ResourceNotFoundException( + "Dependency type not found with id: " + request.getDependencyTypeId())); + dependency.setDependencyType(dependencyType); + } + + if (request.getName() != null) { + dependency.setName(request.getName()); + } + + if (request.getDescription() != null) { + dependency.setDescription(request.getDescription()); + } + + if (request.getTechnicalDocumentation() != null) { + dependency.setTechnicalDocumentation(request.getTechnicalDocumentation()); + } + + if (request.getValidityStartDate() != null) { + dependency.setValidityStartDate(request.getValidityStartDate()); + } + + if (request.getValidityEndDate() != null) { + dependency.setValidityEndDate(request.getValidityEndDate()); + } + + // Validate dates after updates + if (dependency.getValidityStartDate() != null && dependency.getValidityEndDate() != null) { + if (dependency.getValidityEndDate().isBefore(dependency.getValidityStartDate())) { + throw new BadRequestException("End date must be after or equal to start date"); + } + } + + dependency = externalDependencyRepository.save(dependency); + return mapToResponse(dependency); + } + + public ExternalDependencyResponse findById(UUID id) { + ExternalDependency dependency = externalDependencyRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("External dependency not found with id: " + id)); + return mapToResponse(dependency); + } + + public Page findByApplication(UUID applicationId, Pageable pageable) { + if (!applicationRepository.existsById(applicationId)) { + throw new ResourceNotFoundException("Application not found with id: " + applicationId); + } + return externalDependencyRepository.findByApplicationId(applicationId, pageable) + .map(this::mapToResponse); + } + + public Page findAll(Pageable pageable) { + return externalDependencyRepository.findAll(pageable).map(this::mapToResponse); + } + + public Page search( + UUID applicationId, + UUID dependencyTypeId, + String status, + Pageable pageable) { + + LocalDate now = LocalDate.now(); + LocalDate expiringDate = now.plusDays(30); + + return externalDependencyRepository.search( + applicationId, dependencyTypeId, status, now, expiringDate, pageable) + .map(this::mapToResponse); + } + + public List findExpiring(int days) { + LocalDate now = LocalDate.now(); + LocalDate expirationDate = now.plusDays(days); + return externalDependencyRepository.findExpiring(now, expirationDate).stream() + .map(this::mapToResponse) + .collect(Collectors.toList()); + } + + public List findExpired() { + LocalDate now = LocalDate.now(); + return externalDependencyRepository.findExpired(now).stream() + .map(this::mapToResponse) + .collect(Collectors.toList()); + } + + @Transactional + public void delete(UUID id) { + if (!externalDependencyRepository.existsById(id)) { + throw new ResourceNotFoundException("External dependency not found with id: " + id); + } + externalDependencyRepository.deleteById(id); + } + + private ExternalDependencyResponse mapToResponse(ExternalDependency dependency) { + ApplicationSummaryResponse appSummary = new ApplicationSummaryResponse( + dependency.getApplication().getId(), + dependency.getApplication().getName(), + dependency.getApplication().getStatus(), + dependency.getApplication().getBusinessUnit().getName() + ); + + DependencyTypeResponse typeResponse = new DependencyTypeResponse( + dependency.getDependencyType().getId(), + dependency.getDependencyType().getTypeName(), + dependency.getDependencyType().getDescription(), + dependency.getDependencyType().getIsCustom(), + dependency.getDependencyType().getCreatedAt(), + dependency.getDependencyType().getUpdatedAt() + ); + + // Compute status + String status = computeStatus(dependency); + Boolean isActive = "ACTIVE".equals(status) || "EXPIRING".equals(status); + Integer daysUntilExpiration = computeDaysUntilExpiration(dependency); + + return new ExternalDependencyResponse( + dependency.getId(), + appSummary, + typeResponse, + dependency.getName(), + dependency.getDescription(), + dependency.getTechnicalDocumentation(), + dependency.getValidityStartDate(), + dependency.getValidityEndDate(), + isActive, + daysUntilExpiration, + status, + dependency.getCreatedAt(), + dependency.getUpdatedAt() + ); + } + + private String computeStatus(ExternalDependency dependency) { + LocalDate now = LocalDate.now(); + + if (dependency.getValidityStartDate() != null && now.isBefore(dependency.getValidityStartDate())) { + return "NOT_YET_VALID"; + } + + if (dependency.getValidityEndDate() == null) { + return "ACTIVE"; // No end date = indefinite + } + + if (now.isAfter(dependency.getValidityEndDate())) { + return "EXPIRED"; + } + + long daysUntilExpiration = ChronoUnit.DAYS.between(now, dependency.getValidityEndDate()); + if (daysUntilExpiration <= 30) { + return "EXPIRING"; + } + + return "ACTIVE"; + } + + private Integer computeDaysUntilExpiration(ExternalDependency dependency) { + if (dependency.getValidityEndDate() == null) { + return null; + } + + LocalDate now = LocalDate.now(); + if (now.isAfter(dependency.getValidityEndDate())) { + return null; // Already expired + } + + return (int) ChronoUnit.DAYS.between(now, dependency.getValidityEndDate()); + } +} diff --git a/backend/src/main/resources/db/changelog/db.changelog-master.xml b/backend/src/main/resources/db/changelog/db.changelog-master.xml index c2966f2..0dcc4f8 100644 --- a/backend/src/main/resources/db/changelog/db.changelog-master.xml +++ b/backend/src/main/resources/db/changelog/db.changelog-master.xml @@ -14,5 +14,6 @@ + diff --git a/backend/src/main/resources/db/changelog/v1.0/009-create-external-dependency-tables.xml b/backend/src/main/resources/db/changelog/v1.0/009-create-external-dependency-tables.xml new file mode 100644 index 0000000..aeb3fc3 --- /dev/null +++ b/backend/src/main/resources/db/changelog/v1.0/009-create-external-dependency-tables.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ALTER TABLE external_dependency + ADD CONSTRAINT check_validity_dates + CHECK (validity_end_date IS NULL OR validity_start_date IS NULL OR validity_end_date >= validity_start_date); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.html b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.html new file mode 100644 index 0000000..7cc43ca --- /dev/null +++ b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.html @@ -0,0 +1,81 @@ +
+
+

External Dependencies

+ +
+ +
+
+ + +
+ +
+ + +
+
+ +
Loading dependencies...
+
{{ error }}
+ +
+ + + + + + + + + + + + + + + + + + + +
NameTypeStatusValidity PeriodActions
{{ dep.name }}{{ dep.dependencyType.typeName }} + + {{ getStatusLabel(dep.status) }} + + ({{ dep.daysUntilExpiration }} days) + + + +
+ {{ dep.validityStartDate ? (dep.validityStartDate | date:'mediumDate') : '-' }} + → + {{ dep.validityEndDate ? (dep.validityEndDate | date:'mediumDate') : 'Indefinite' }} +
+
-
+
+ + + +
+
+ +
+ No external dependencies found for this application. +
+ + +
diff --git a/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.scss b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.scss new file mode 100644 index 0000000..d70297e --- /dev/null +++ b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.scss @@ -0,0 +1,181 @@ +.dependencies-container { + .header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + + h3 { + margin: 0; + } + } +} + +.btn-primary { + background-color: #3f51b5; + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #303f9f; + } +} + +.filters { + display: flex; + gap: 1rem; + margin-bottom: 1.5rem; + padding: 1rem; + background: #f9f9f9; + border-radius: 8px; + + .filter-group { + flex: 1; + min-width: 200px; + + label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #555; + } + + .filter-select { + width: 100%; + padding: 0.5rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.95rem; + + &:focus { + outline: none; + border-color: #3f51b5; + } + } + } +} + +.loading, .error, .empty { + text-align: center; + padding: 2rem; + color: #666; +} + +.error { + color: #f44336; +} + +.dependencies-table { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + + table { + width: 100%; + border-collapse: collapse; + + th, td { + padding: 1rem; + text-align: left; + border-bottom: 1px solid #f5f5f5; + } + + th { + background-color: #f9f9f9; + font-weight: 600; + color: #555; + } + + tbody tr:hover { + background-color: #fafafa; + } + } +} + +.status-badge { + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 500; + display: inline-block; + + &.status-active { + background-color: #e8f5e9; + color: #388e3c; + } + + &.status-expiring { + background-color: #fff3e0; + color: #f57c00; + } + + &.status-expired { + background-color: #ffebee; + color: #c62828; + } + + &.status-not-valid { + background-color: #f5f5f5; + color: #616161; + } +} + +.actions { + display: flex; + gap: 0.5rem; +} + +.btn-sm { + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + cursor: pointer; + background-color: #2196f3; + color: white; + font-size: 0.875rem; + + &:hover { + background-color: #1976d2; + } + + &.btn-danger { + background-color: #f44336; + + &:hover { + background-color: #d32f2f; + } + } +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin-top: 1.5rem; + + button { + padding: 0.5rem 1rem; + border: 1px solid #ddd; + border-radius: 4px; + background: white; + cursor: pointer; + + &:hover:not(:disabled) { + background-color: #f5f5f5; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + span { + color: #666; + } +} diff --git a/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.ts b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.ts new file mode 100644 index 0000000..2f77af2 --- /dev/null +++ b/frontend/src/app/features/applications/application-dependencies/application-dependencies.component.ts @@ -0,0 +1,152 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DependencyService } from '../../dependencies/dependency.service'; +import { ExternalDependency, DependencyType } from '../../../shared/models/dependency.model'; +import { Page } from '../../../shared/models/environment.model'; + +@Component({ + selector: 'app-application-dependencies', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './application-dependencies.component.html', + styleUrls: ['./application-dependencies.component.scss'] +}) +export class ApplicationDependenciesComponent implements OnInit { + @Input() applicationId!: string; + @Input() applicationName!: string; + + dependencies: ExternalDependency[] = []; + dependencyTypes: DependencyType[] = []; + loading = false; + error = ''; + + page = 0; + size = 10; + totalPages = 0; + + selectedTypeId = ''; + selectedStatus = ''; + + statusOptions = [ + { value: '', label: 'All Statuses' }, + { value: 'ACTIVE', label: 'Active' }, + { value: 'EXPIRING', label: 'Expiring Soon' }, + { value: 'EXPIRED', label: 'Expired' }, + { value: 'NOT_YET_VALID', label: 'Not Yet Valid' } + ]; + + constructor( + private dependencyService: DependencyService, + private router: Router + ) {} + + ngOnInit(): void { + if (this.applicationId) { + this.loadDependencyTypes(); + this.loadDependencies(); + } + } + + loadDependencyTypes(): void { + this.dependencyService.getDependencyTypes().subscribe({ + next: (types) => { + this.dependencyTypes = types; + }, + error: (err) => { + console.error('Failed to load dependency types', err); + } + }); + } + + loadDependencies(): void { + this.loading = true; + const filters: any = { applicationId: this.applicationId }; + + if (this.selectedTypeId) { + filters.dependencyTypeId = this.selectedTypeId; + } + if (this.selectedStatus) { + filters.status = this.selectedStatus; + } + + this.dependencyService.getDependencies(filters, this.page, this.size).subscribe({ + next: (data: Page) => { + this.dependencies = data.content; + this.totalPages = data.totalPages; + this.loading = false; + }, + error: (err) => { + this.error = 'Failed to load dependencies'; + this.loading = false; + } + }); + } + + onFilterChange(): void { + this.page = 0; + this.loadDependencies(); + } + + createNew(): void { + this.router.navigate(['/dependencies/new'], { + queryParams: { applicationId: this.applicationId } + }); + } + + viewDetails(id: string): void { + this.router.navigate(['/dependencies', id]); + } + + edit(id: string): void { + this.router.navigate(['/dependencies', id, 'edit']); + } + + delete(id: string): void { + if (confirm('Are you sure you want to delete this dependency?')) { + this.dependencyService.deleteDependency(id).subscribe({ + next: () => { + this.loadDependencies(); + }, + error: (err) => { + this.error = 'Failed to delete dependency'; + } + }); + } + } + + getStatusClass(status: string): string { + const classes: Record = { + 'ACTIVE': 'status-active', + 'EXPIRING': 'status-expiring', + 'EXPIRED': 'status-expired', + 'NOT_YET_VALID': 'status-not-valid' + }; + return classes[status] || ''; + } + + getStatusLabel(status: string): string { + const labels: Record = { + 'ACTIVE': 'Active', + 'EXPIRING': 'Expiring Soon', + 'EXPIRED': 'Expired', + 'NOT_YET_VALID': 'Not Yet Valid' + }; + return labels[status] || status; + } + + nextPage(): void { + if (this.page < this.totalPages - 1) { + this.page++; + this.loadDependencies(); + } + } + + previousPage(): void { + if (this.page > 0) { + this.page--; + this.loadDependencies(); + } + } +} diff --git a/frontend/src/app/features/dependencies/dependency.service.ts b/frontend/src/app/features/dependencies/dependency.service.ts new file mode 100644 index 0000000..b1b7a30 --- /dev/null +++ b/frontend/src/app/features/dependencies/dependency.service.ts @@ -0,0 +1,123 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { + ExternalDependency, + CreateExternalDependencyRequest, + UpdateExternalDependencyRequest, + DependencyType, + CreateDependencyTypeRequest, + UpdateDependencyTypeRequest +} from '../../shared/models/dependency.model'; +import { Page } from '../../shared/models/environment.model'; + +@Injectable({ + providedIn: 'root' +}) +export class DependencyService { + private readonly API_URL = '/api/dependencies'; + private readonly TYPE_API_URL = '/api/dependency-types'; + + constructor(private http: HttpClient) {} + + // Dependency Types + getDependencyTypes(): Observable { + return this.http.get(this.TYPE_API_URL); + } + + getDependencyType(id: string): Observable { + return this.http.get(`${this.TYPE_API_URL}/${id}`); + } + + createDependencyType(data: CreateDependencyTypeRequest): Observable { + return this.http.post(this.TYPE_API_URL, data); + } + + updateDependencyType(id: string, data: UpdateDependencyTypeRequest): Observable { + return this.http.put(`${this.TYPE_API_URL}/${id}`, data); + } + + deleteDependencyType(id: string): Observable { + return this.http.delete(`${this.TYPE_API_URL}/${id}`); + } + + // External Dependencies + getDependencies( + filters?: { + applicationId?: string; + dependencyTypeId?: string; + status?: string; + }, + page: number = 0, + size: number = 20, + sortBy: string = 'name', + sortDirection: string = 'asc' + ): Observable> { + let params = new HttpParams() + .set('page', page.toString()) + .set('size', size.toString()) + .set('sortBy', sortBy) + .set('sortDirection', sortDirection); + + if (filters?.applicationId) { + params = params.set('applicationId', filters.applicationId); + } + if (filters?.dependencyTypeId) { + params = params.set('dependencyTypeId', filters.dependencyTypeId); + } + if (filters?.status) { + params = params.set('status', filters.status); + } + + return this.http.get>(this.API_URL, { params }); + } + + getDependency(id: string): Observable { + return this.http.get(`${this.API_URL}/${id}`); + } + + getDependenciesByApplication( + applicationId: string, + page: number = 0, + size: number = 20 + ): Observable> { + const params = new HttpParams() + .set('page', page.toString()) + .set('size', size.toString()); + + return this.http.get>( + `${this.API_URL}/by-application/${applicationId}`, + { params } + ); + } + + createDependency( + applicationId: string, + data: CreateExternalDependencyRequest + ): Observable { + return this.http.post( + `${this.API_URL}/for-application/${applicationId}`, + data + ); + } + + updateDependency( + id: string, + data: UpdateExternalDependencyRequest + ): Observable { + return this.http.put(`${this.API_URL}/${id}`, data); + } + + deleteDependency(id: string): Observable { + return this.http.delete(`${this.API_URL}/${id}`); + } + + getExpiringDependencies(days: number = 30): Observable { + const params = new HttpParams().set('days', days.toString()); + return this.http.get(`${this.API_URL}/expiring`, { params }); + } + + getExpiredDependencies(): Observable { + return this.http.get(`${this.API_URL}/expired`); + } +} diff --git a/frontend/src/app/shared/models/dependency.model.ts b/frontend/src/app/shared/models/dependency.model.ts new file mode 100644 index 0000000..dcc55cf --- /dev/null +++ b/frontend/src/app/shared/models/dependency.model.ts @@ -0,0 +1,55 @@ +export interface DependencyType { + id: string; + typeName: string; + description?: string; + isCustom: boolean; + createdAt: Date; + updatedAt: Date; +} + +export interface ExternalDependency { + id: string; + application: { + id: string; + name: string; + }; + dependencyType: DependencyType; + name: string; + description?: string; + technicalDocumentation?: string; + validityStartDate?: Date; + validityEndDate?: Date; + isActive: boolean; + daysUntilExpiration?: number; + status: 'ACTIVE' | 'EXPIRING' | 'EXPIRED' | 'NOT_YET_VALID'; + createdAt: Date; + updatedAt: Date; +} + +export interface CreateDependencyTypeRequest { + typeName: string; + description?: string; +} + +export interface UpdateDependencyTypeRequest { + typeName?: string; + description?: string; +} + +export interface CreateExternalDependencyRequest { + dependencyTypeId: string; + name: string; + description?: string; + technicalDocumentation?: string; + validityStartDate?: Date; + validityEndDate?: Date; +} + +export interface UpdateExternalDependencyRequest { + dependencyTypeId?: string; + name?: string; + description?: string; + technicalDocumentation?: string; + validityStartDate?: Date; + validityEndDate?: Date; +}