autocomit
This commit is contained in:
@@ -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
|
||||
@@ -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<DependencyTypeResponse> 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<DependencyTypeResponse> 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<DependencyTypeResponse> 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<List<DependencyTypeResponse>> getAll() {
|
||||
List<DependencyTypeResponse> 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<Void> delete(@PathVariable UUID id) {
|
||||
dependencyTypeService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -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<ExternalDependencyResponse> 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<ExternalDependencyResponse> 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<ExternalDependencyResponse> 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<Page<ExternalDependencyResponse>> 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<ExternalDependencyResponse> 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<Page<ExternalDependencyResponse>> 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<ExternalDependencyResponse> 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<List<ExternalDependencyResponse>> getExpiring(
|
||||
@RequestParam(defaultValue = "30") int days) {
|
||||
List<ExternalDependencyResponse> response = externalDependencyService.findExpiring(days);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@GetMapping("/expired")
|
||||
@Operation(summary = "Get expired dependencies", description = "Get all expired dependencies")
|
||||
public ResponseEntity<List<ExternalDependencyResponse>> getExpired() {
|
||||
List<ExternalDependencyResponse> response = externalDependencyService.findExpired();
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "Delete dependency", description = "Delete external dependency")
|
||||
public ResponseEntity<Void> delete(@PathVariable UUID id) {
|
||||
externalDependencyService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<DependencyType, UUID> {
|
||||
Optional<DependencyType> findByTypeName(String typeName);
|
||||
boolean existsByTypeName(String typeName);
|
||||
}
|
||||
@@ -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<ExternalDependency, UUID> {
|
||||
|
||||
Page<ExternalDependency> findByApplicationId(UUID applicationId, Pageable pageable);
|
||||
|
||||
Page<ExternalDependency> 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<ExternalDependency> 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<ExternalDependency> 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<ExternalDependency> 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);
|
||||
}
|
||||
@@ -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<DependencyTypeResponse> 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<ExternalDependencyResponse> 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<ExternalDependencyResponse> findAll(Pageable pageable) {
|
||||
return externalDependencyRepository.findAll(pageable).map(this::mapToResponse);
|
||||
}
|
||||
|
||||
public Page<ExternalDependencyResponse> 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<ExternalDependencyResponse> 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<ExternalDependencyResponse> 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());
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,6 @@
|
||||
<include file="db/changelog/v1.0/006-create-version-table.xml"/>
|
||||
<include file="db/changelog/v1.0/007-create-deployment-table.xml"/>
|
||||
<include file="db/changelog/v1.0/008-create-application-contact-table.xml"/>
|
||||
<include file="db/changelog/v1.0/009-create-external-dependency-tables.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="009-create-external-dependency-tables" author="ldpv2-team">
|
||||
|
||||
<!-- Dependency Types Catalog -->
|
||||
<createTable tableName="dependency_type">
|
||||
<column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="type_name" type="VARCHAR(100)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column name="description" type="TEXT"/>
|
||||
<column name="is_custom" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<!-- External Dependencies -->
|
||||
<createTable tableName="external_dependency">
|
||||
<column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="application_id" type="UUID">
|
||||
<constraints nullable="false"
|
||||
foreignKeyName="fk_ext_dep_application"
|
||||
references="application(id)"
|
||||
deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="dependency_type_id" type="UUID">
|
||||
<constraints nullable="false"
|
||||
foreignKeyName="fk_ext_dep_type"
|
||||
references="dependency_type(id)"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="TEXT"/>
|
||||
<column name="technical_documentation" type="TEXT"/>
|
||||
<column name="validity_start_date" type="DATE"/>
|
||||
<column name="validity_end_date" type="DATE"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<!-- Add check constraint for validity dates -->
|
||||
<sql>
|
||||
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);
|
||||
</sql>
|
||||
|
||||
<!-- Indexes -->
|
||||
<createIndex tableName="dependency_type" indexName="idx_dep_type_name">
|
||||
<column name="type_name"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex tableName="external_dependency" indexName="idx_ext_dep_application">
|
||||
<column name="application_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex tableName="external_dependency" indexName="idx_ext_dep_type">
|
||||
<column name="dependency_type_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex tableName="external_dependency" indexName="idx_ext_dep_validity_end">
|
||||
<column name="validity_end_date"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex tableName="external_dependency" indexName="idx_ext_dep_name">
|
||||
<column name="name"/>
|
||||
</createIndex>
|
||||
|
||||
<!-- Insert default dependency types -->
|
||||
<insert tableName="dependency_type">
|
||||
<column name="type_name" value="WEB_SERVICE"/>
|
||||
<column name="description" value="REST APIs, SOAP services, microservices"/>
|
||||
<column name="is_custom" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="dependency_type">
|
||||
<column name="type_name" value="DATABASE"/>
|
||||
<column name="description" value="External database connections"/>
|
||||
<column name="is_custom" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="dependency_type">
|
||||
<column name="type_name" value="CERTIFICATE"/>
|
||||
<column name="description" value="SSL/TLS certificates, authentication certificates"/>
|
||||
<column name="is_custom" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="dependency_type">
|
||||
<column name="type_name" value="NETWORK_FLOW"/>
|
||||
<column name="description" value="Network connections, firewall rules, VPN tunnels"/>
|
||||
<column name="is_custom" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
<!-- Sample data -->
|
||||
<insert tableName="external_dependency">
|
||||
<column name="application_id" valueComputed="(SELECT id FROM application WHERE name = 'Customer Portal' LIMIT 1)"/>
|
||||
<column name="dependency_type_id" valueComputed="(SELECT id FROM dependency_type WHERE type_name = 'WEB_SERVICE' LIMIT 1)"/>
|
||||
<column name="name" value="Payment Gateway API"/>
|
||||
<column name="description" value="External payment processing service"/>
|
||||
<column name="technical_documentation" value="Endpoint: https://api.payment.example.com/v2"/>
|
||||
<column name="validity_start_date" value="2024-01-01"/>
|
||||
<column name="validity_end_date" value="2027-12-31"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="external_dependency">
|
||||
<column name="application_id" valueComputed="(SELECT id FROM application WHERE name = 'Internal CRM' LIMIT 1)"/>
|
||||
<column name="dependency_type_id" valueComputed="(SELECT id FROM dependency_type WHERE type_name = 'DATABASE' LIMIT 1)"/>
|
||||
<column name="name" value="Legacy Customer Database"/>
|
||||
<column name="description" value="Read-only connection to legacy system"/>
|
||||
<column name="technical_documentation" value="Server: legacy-db.internal:5432"/>
|
||||
<column name="validity_start_date" value="2020-01-01"/>
|
||||
<column name="validity_end_date" value="2026-06-30"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user