diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/controller/SystemInfoController.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/controller/SystemInfoController.java index 2e0cf0b..05f80da 100644 --- a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/controller/SystemInfoController.java +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/controller/SystemInfoController.java @@ -4,13 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.psg.dlsysinfo.dl_sysinfo_server.dto.InstalledAppDTO; import com.psg.dlsysinfo.dl_sysinfo_server.dto.PatchComplianceDTO; import com.psg.dlsysinfo.dl_sysinfo_server.dto.SystemInfoDTO; -import com.psg.dlsysinfo.dl_sysinfo_server.entity.Client; -import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices; -import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledSoftware; -import com.psg.dlsysinfo.dl_sysinfo_server.entity.WindowsUpdate; +import com.psg.dlsysinfo.dl_sysinfo_server.entity.*; import com.psg.dlsysinfo.dl_sysinfo_server.repository.ClientRepository; import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository; -import com.psg.dlsysinfo.dl_sysinfo_server.repository.WindowsUpdateRepository; +import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledPatchRepository; +import com.psg.dlsysinfo.dl_sysinfo_server.repository.WindowsUpdateHistoryRepository; +import com.psg.dlsysinfo.dl_sysinfo_server.repository.PatchComplianceSummaryRepository; import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService; import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil; import com.psg.dlsysinfo.dl_sysinfo_server.security.TokenResolver; @@ -45,7 +44,9 @@ public class SystemInfoController { @Autowired private ClientRepository clientRepository; @Autowired private InstalledSoftwareRepository installedSoftwareRepository; - @Autowired private WindowsUpdateRepository windowsUpdateRepository; + @Autowired private InstalledPatchRepository installedPatchRepository; + @Autowired private WindowsUpdateHistoryRepository windowsUpdateHistoryRepository; + @Autowired private PatchComplianceSummaryRepository patchComplianceSummaryRepository; @Autowired private DeviceService deviceService; @Autowired private ObjectMapper objectMapper; @@ -148,7 +149,7 @@ public class SystemInfoController { public ResponseEntity> receivePatchCompliance( @AuthenticationPrincipal Object currentUser, HttpServletRequest request, - @RequestBody List patchData) { + @RequestBody PatchComplianceDTO patchData) { Map response = new HashMap<>(); @@ -158,7 +159,7 @@ public class SystemInfoController { return errorResponse(response, "Invalid or expired token.", 403); } - if (patchData == null || patchData.isEmpty()) { + if (patchData == null) { return errorResponse(response, "No patch data found in request body.", 400); } @@ -168,35 +169,86 @@ public class SystemInfoController { Client client = clientRepository.findByClientIdentifier(clientIdentifier) .orElseThrow(() -> new IllegalArgumentException("Client not registered")); - // Get the device associated with this client - // Since we need to know which device this is for, we'll need the hostname - // For now, we'll assume the most recently checked-in device for this client - Devices device = deviceService.getMostRecentDeviceForClient(client); - if (device == null) { - return errorResponse(response, "No device found for this client.", 404); + // Get device using hostname from the payload + String hostname = patchData.getHostname(); + if (hostname == null || hostname.isEmpty()) { + return errorResponse(response, "Hostname is required in patch compliance data.", 400); } - System.out.println("🔍 Processing " + patchData.size() + " Windows updates for device: " + device.getDeviceId()); + String hashedHostname = encryptionService.hashString(hostname); + Devices device = deviceService.findOrCreateDevice(hostname, hashedHostname, client); - // Delete existing updates for this device to replace with fresh data - windowsUpdateRepository.deleteByDevice(device); + System.out.println("🔍 Processing patch compliance for device: " + device.getDeviceId() + " (" + hostname + ")"); - // Save new updates - for (PatchComplianceDTO dto : patchData) { - if (dto.getUpdateId() == null) continue; + // Step 1: Delete and insert installed patches + installedPatchRepository.deleteByDevice(device); - WindowsUpdate update = new WindowsUpdate(); - update.setDevice(device); - update.setDate(dto.getDate()); - update.setTitle(dto.getTitle()); - update.setUpdateId(dto.getUpdateId()); + int patchesProcessed = 0; + if (patchData.getInstalledPatches() != null) { + for (var patchDTO : patchData.getInstalledPatches()) { + if (patchDTO.getHotFixId() == null) continue; - windowsUpdateRepository.save(update); + InstalledPatch patch = new InstalledPatch(); + patch.setDevice(device); + patch.setHotfixId(patchDTO.getHotFixId()); + patch.setCaption(patchDTO.getCaption()); + patch.setDescription(patchDTO.getDescription()); + patch.setInstalledBy(patchDTO.getInstalledBy()); + patch.setInstalledOn(patchDTO.getInstalledOn()); + patch.setRecordedAt(LocalDateTime.now()); + + installedPatchRepository.save(patch); + patchesProcessed++; + } } + // Step 2: Delete and insert update history + windowsUpdateHistoryRepository.deleteByDevice(device); + + int historyProcessed = 0; + if (patchData.getRecentUpdateHistory() != null) { + for (var historyDTO : patchData.getRecentUpdateHistory()) { + WindowsUpdateHistory history = new WindowsUpdateHistory(); + history.setDevice(device); + history.setUpdateDate(historyDTO.getDate()); + history.setTitle(historyDTO.getTitle()); + history.setUpdateId(historyDTO.getUpdateIdentity()); + history.setOperation(historyDTO.getOperation()); + history.setResultCode(historyDTO.getResultCode()); + history.setRecordedAt(LocalDateTime.now()); + + windowsUpdateHistoryRepository.save(history); + historyProcessed++; + } + } + + // Step 3: Update or insert summary + long successfulUpdates = patchData.getRecentUpdateHistory() != null + ? patchData.getRecentUpdateHistory().stream() + .filter(h -> "Succeeded".equalsIgnoreCase(h.getResultCode())) + .count() + : 0; + + PatchComplianceSummary summary = patchComplianceSummaryRepository + .findByDevice(device) + .orElseGet(() -> { + PatchComplianceSummary newSummary = new PatchComplianceSummary(); + newSummary.setDevice(device); + return newSummary; + }); + + summary.setTotalInstalledPatches(patchesProcessed); + summary.setRecentSuccessfulUpdates((int) successfulUpdates); + summary.setLastCollectedAt(LocalDateTime.now()); + summary.setRecordedAt(LocalDateTime.now()); + + patchComplianceSummaryRepository.save(summary); + response.put("status", "success"); response.put("message", "Patch compliance data received and stored successfully."); - response.put("updatesProcessed", String.valueOf(patchData.size())); + response.put("patchesProcessed", String.valueOf(patchesProcessed)); + response.put("historyProcessed", String.valueOf(historyProcessed)); + response.put("successfulUpdates", String.valueOf(successfulUpdates)); return ResponseEntity.ok(response); } catch (Exception e) { diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/InstalledPatchDTO.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/InstalledPatchDTO.java new file mode 100644 index 0000000..7217ed3 --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/InstalledPatchDTO.java @@ -0,0 +1,24 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class InstalledPatchDTO { + @JsonProperty("HotFixID") + private String hotFixId; + + @JsonProperty("Caption") + private String caption; + + @JsonProperty("Description") + private String description; + + @JsonProperty("InstalledBy") + private String installedBy; + + @JsonProperty("InstalledOn") + private String installedOn; +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/PatchComplianceDTO.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/PatchComplianceDTO.java index a952448..4f4adbe 100644 --- a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/PatchComplianceDTO.java +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/PatchComplianceDTO.java @@ -3,10 +3,12 @@ package com.psg.dlsysinfo.dl_sysinfo_server.dto; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @NoArgsConstructor public class PatchComplianceDTO { - private String date; - private String title; - private String updateId; + private String hostname; + private List installedPatches; + private List recentUpdateHistory; } diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/UpdateHistoryDTO.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/UpdateHistoryDTO.java new file mode 100644 index 0000000..be5657a --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/dto/UpdateHistoryDTO.java @@ -0,0 +1,14 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UpdateHistoryDTO { + private String date; + private String title; + private String updateIdentity; + private String operation; + private String resultCode; +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/InstalledPatch.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/InstalledPatch.java new file mode 100644 index 0000000..64f4117 --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/InstalledPatch.java @@ -0,0 +1,47 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "installed_patches", + uniqueConstraints = @UniqueConstraint(name = "unique_device_patch", columnNames = {"device_id", "hotfix_id"}), + indexes = { + @Index(name = "idx_device_hotfix", columnList = "device_id, hotfix_id"), + @Index(name = "idx_installed_on", columnList = "installed_on") + }) +@Getter +@Setter +@NoArgsConstructor +public class InstalledPatch { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "device_id", nullable = false) + private Devices device; + + @Column(name = "hotfix_id", nullable = false, length = 50) + private String hotfixId; + + @Column(name = "caption", length = 500) + private String caption; + + @Column(name = "description", length = 255) + private String description; + + @Column(name = "installed_by", length = 255) + private String installedBy; + + @Column(name = "installed_on", length = 50) + private String installedOn; + + @Column(name = "recorded_at", nullable = false) + private LocalDateTime recordedAt = LocalDateTime.now(); +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/PatchComplianceSummary.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/PatchComplianceSummary.java new file mode 100644 index 0000000..3ac446f --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/PatchComplianceSummary.java @@ -0,0 +1,40 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "patch_compliance_summary", + uniqueConstraints = @UniqueConstraint(name = "unique_device_summary", columnNames = {"device_id"}), + indexes = { + @Index(name = "idx_last_collected", columnList = "last_collected_at") + }) +@Getter +@Setter +@NoArgsConstructor +public class PatchComplianceSummary { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(name = "device_id", nullable = false) + private Devices device; + + @Column(name = "total_installed_patches", nullable = false) + private Integer totalInstalledPatches = 0; + + @Column(name = "recent_successful_updates", nullable = false) + private Integer recentSuccessfulUpdates = 0; + + @Column(name = "last_collected_at", nullable = false) + private LocalDateTime lastCollectedAt; + + @Column(name = "recorded_at", nullable = false) + private LocalDateTime recordedAt = LocalDateTime.now(); +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/WindowsUpdateHistory.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/WindowsUpdateHistory.java new file mode 100644 index 0000000..c980732 --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/entity/WindowsUpdateHistory.java @@ -0,0 +1,46 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "windows_update_history", + indexes = { + @Index(name = "idx_device_date", columnList = "device_id, update_date"), + @Index(name = "idx_result_code", columnList = "result_code") + }) +@Getter +@Setter +@NoArgsConstructor +public class WindowsUpdateHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "device_id", nullable = false) + private Devices device; + + @Column(name = "update_date", length = 255) + private String updateDate; + + @Column(name = "title", length = 1000) + private String title; + + @Column(name = "update_id", length = 255) + private String updateId; + + @Column(name = "operation", length = 50) + private String operation; + + @Column(name = "result_code", length = 50) + private String resultCode; + + @Column(name = "recorded_at", nullable = false) + private LocalDateTime recordedAt = LocalDateTime.now(); +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/InstalledPatchRepository.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/InstalledPatchRepository.java new file mode 100644 index 0000000..bd72caa --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/InstalledPatchRepository.java @@ -0,0 +1,11 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.repository; + +import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices; +import com.psg.dlsysinfo.dl_sysinfo_server.entity.InstalledPatch; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface InstalledPatchRepository extends JpaRepository { + void deleteByDevice(Devices device); +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/PatchComplianceSummaryRepository.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/PatchComplianceSummaryRepository.java new file mode 100644 index 0000000..bf11099 --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/PatchComplianceSummaryRepository.java @@ -0,0 +1,13 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.repository; + +import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices; +import com.psg.dlsysinfo.dl_sysinfo_server.entity.PatchComplianceSummary; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PatchComplianceSummaryRepository extends JpaRepository { + Optional findByDevice(Devices device); +} diff --git a/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/WindowsUpdateHistoryRepository.java b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/WindowsUpdateHistoryRepository.java new file mode 100644 index 0000000..31bba7c --- /dev/null +++ b/src/main/java/com/psg/dlsysinfo/dl_sysinfo_server/repository/WindowsUpdateHistoryRepository.java @@ -0,0 +1,11 @@ +package com.psg.dlsysinfo.dl_sysinfo_server.repository; + +import com.psg.dlsysinfo.dl_sysinfo_server.entity.Devices; +import com.psg.dlsysinfo.dl_sysinfo_server.entity.WindowsUpdateHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WindowsUpdateHistoryRepository extends JpaRepository { + void deleteByDevice(Devices device); +}