Introduction of WindowsUpdate endpoint, allows for /api/patch-compliance to accept POSTS
All checks were successful
Build & Deploy Backend / build (push) Successful in 1m6s
Build & Deploy Backend / deploy (push) Successful in 32s

This commit is contained in:
2025-11-05 08:45:48 +08:00
parent 1a08230291
commit 6be9c671ff
6 changed files with 7046 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,15 @@ package com.psg.dlsysinfo.dl_sysinfo_server.controller;
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.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.security.EncryptionService;
import com.psg.dlsysinfo.dl_sysinfo_server.security.JwtUtil;
import com.psg.dlsysinfo.dl_sysinfo_server.security.TokenResolver;
@@ -42,6 +45,7 @@ public class SystemInfoController {
@Autowired private ClientRepository clientRepository;
@Autowired private InstalledSoftwareRepository installedSoftwareRepository;
@Autowired private WindowsUpdateRepository windowsUpdateRepository;
@Autowired private DeviceService deviceService;
@Autowired private ObjectMapper objectMapper;
@@ -67,18 +71,25 @@ public class SystemInfoController {
String decryptedData = encryptionService.decryptData(encryptedData);
System.out.println("🔓 Decrypted payload size: " + decryptedData.length());
System.out.println("🔓 Decrypted payload content: " + decryptedData);
SystemInfoDTO dto;
try {
dto = objectMapper.readValue(decryptedData, SystemInfoDTO.class);
System.out.println("✅ Successfully deserialized SystemInfoDTO");
} catch (Exception ex) {
System.out.println("❌ Failed to deserialize SystemInfoDTO: " + ex.getMessage());
ex.printStackTrace();
return errorResponse(response, "Failed to parse payload.", 400);
}
System.out.println("🔍 DTO clientIdentifier: " + dto.getClientIdentifier());
System.out.println("🔍 DTO hostname: " + dto.getHostname());
System.out.println("🔍 DTO drives: " + (dto.getDrives() != null ? dto.getDrives().size() : "null"));
if (dto.getClientIdentifier() == null || dto.getHostname() == null) {
System.out.println("❌ Missing required fields");
return errorResponse(response, "Missing clientIdentifier or hostname in payload.", 400);
}
@@ -132,6 +143,69 @@ public class SystemInfoController {
}
}
@PreAuthorize("isAuthenticated()")
@PostMapping("/patch-compliance")
public ResponseEntity<Map<String, String>> receivePatchCompliance(
@AuthenticationPrincipal Object currentUser,
HttpServletRequest request,
@RequestBody List<PatchComplianceDTO> patchData) {
Map<String, String> response = new HashMap<>();
try {
String token = tokenResolver.resolveToken(request);
if (!jwtUtil.validateToken(token)) {
return errorResponse(response, "Invalid or expired token.", 403);
}
if (patchData == null || patchData.isEmpty()) {
return errorResponse(response, "No patch data found in request body.", 400);
}
String clientIdentifier = jwtUtil.extractClientIdentifier(token);
System.out.println("🔍 Receiving patch compliance data for client: " + clientIdentifier);
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);
}
System.out.println("🔍 Processing " + patchData.size() + " Windows updates for device: " + device.getDeviceId());
// Delete existing updates for this device to replace with fresh data
windowsUpdateRepository.deleteByDevice(device);
// Save new updates
for (PatchComplianceDTO dto : patchData) {
if (dto.getUpdateId() == null) continue;
WindowsUpdate update = new WindowsUpdate();
update.setDevice(device);
update.setDate(dto.getDate());
update.setTitle(dto.getTitle());
update.setUpdateId(dto.getUpdateId());
windowsUpdateRepository.save(update);
}
response.put("status", "success");
response.put("message", "Patch compliance data received and stored successfully.");
response.put("updatesProcessed", String.valueOf(patchData.size()));
return ResponseEntity.ok(response);
} catch (Exception e) {
System.out.println("❌ Unexpected exception in patch-compliance: " + e.getMessage());
e.printStackTrace();
return errorResponse(response, "Server error occurred: " + e.getMessage(), 500);
}
}
private ResponseEntity<Map<String, String>> errorResponse(Map<String, String> response, String message, int statusCode) {
response.put("status", "error");
response.put("message", message);

View File

@@ -0,0 +1,12 @@
package com.psg.dlsysinfo.dl_sysinfo_server.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class PatchComplianceDTO {
private String date;
private String title;
private String updateId;
}

View File

@@ -0,0 +1,36 @@
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_updates")
@Getter
@Setter
@NoArgsConstructor
public class WindowsUpdate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "device_id", nullable = false)
private Devices device;
@Column(name = "update_date")
private String date;
@Column(name = "title", length = 1000)
private String title;
@Column(name = "update_id")
private String updateId;
@Column(name = "recorded_at", nullable = false)
private LocalDateTime recordedAt = LocalDateTime.now();
}

View File

@@ -0,0 +1,19 @@
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.WindowsUpdate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface WindowsUpdateRepository extends JpaRepository<WindowsUpdate, Long> {
List<WindowsUpdate> findByDevice(Devices device);
Optional<WindowsUpdate> findByDeviceAndUpdateId(Devices device, String updateId);
void deleteByDevice(Devices device);
}

View File

@@ -293,6 +293,16 @@ public class DeviceService {
System.out.println("🔄 Reassigned device " + deviceId + " to client " + newClientId);
}
public Devices getMostRecentDeviceForClient(Client client) {
List<Devices> devices = devicesRepository.findByClient_ClientId(client.getClientId());
if (devices.isEmpty()) {
return null;
}
return devices.stream()
.max(Comparator.comparing(Devices::getLastCheckedIn))
.orElse(null);
}