Additional PatchHistory entities to expand the information stored.
All checks were successful
Build & Deploy Backend / build (push) Successful in 49s
Build & Deploy Backend / deploy (push) Successful in 32s

This commit is contained in:
2025-11-05 09:22:29 +08:00
parent 6be9c671ff
commit be8e5b0cdf
10 changed files with 290 additions and 30 deletions

View File

@@ -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<Map<String, String>> receivePatchCompliance(
@AuthenticationPrincipal Object currentUser,
HttpServletRequest request,
@RequestBody List<PatchComplianceDTO> patchData) {
@RequestBody PatchComplianceDTO patchData) {
Map<String, String> 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) {

View File

@@ -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;
}

View File

@@ -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<InstalledPatchDTO> installedPatches;
private List<UpdateHistoryDTO> recentUpdateHistory;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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<InstalledPatch, Long> {
void deleteByDevice(Devices device);
}

View File

@@ -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<PatchComplianceSummary, Long> {
Optional<PatchComplianceSummary> findByDevice(Devices device);
}

View File

@@ -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<WindowsUpdateHistory, Long> {
void deleteByDevice(Devices device);
}