From 8e26a73243b5f4453ed6ff7e88109a6593b25a2e Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 15 Jun 2025 09:03:47 +0200 Subject: [PATCH] Add build management features and views --- .../pcinv/controllers/WebController.java | 54 +++++++++++ .../pcinv/meta/AssetDescriptor.java | 13 +++ .../pcinv/meta/AssetDescriptors.java | 31 ++++++ .../seeseepuff/pcinv/meta/AssetProperty.java | 3 + .../be/seeseepuff/pcinv/models/Build.java | 22 ++++- .../be/seeseepuff/pcinv/models/BuildInfo.java | 20 ++++ .../pcinv/repositories/BuildRepository.java | 7 ++ .../pcinv/services/AssetService.java | 9 +- .../pcinv/services/BuildService.java | 94 ++++++++++++++++--- src/main/resources/templates/build_view.html | 24 +++++ src/main/resources/templates/builds.html | 28 ++++++ src/main/resources/templates/view.html | 3 +- 12 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 src/main/java/be/seeseepuff/pcinv/models/BuildInfo.java create mode 100644 src/main/resources/templates/build_view.html create mode 100644 src/main/resources/templates/builds.html diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 3650d26..a0573d2 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -43,6 +43,11 @@ public class WebController { private static final String INPUT_LIST = "inputLists"; /// The name of the model attribute that holds the current work log entries. private static final String WORKLOG = "worklog"; + /// The name of the model attribute that holds the current build being viewed or edited. + private static final String BUILD = "build"; + /// The name of the model attribute that holds the build information. + private static final String BUILD_INFO = "buildInfo"; + /// The name of the model attribute that holds the builds available for selection. private static final String BUILDS = "builds"; /// The name of the input field for the current size of the work log. @@ -93,6 +98,54 @@ public class WebController { return "browse_type"; } + /** + * Handles the browsing of all builds. + * Displays a list of all builds available in the system. + */ + @GetMapping("/builds") + public String browseBuilds(Model model) { + model.addAttribute(TIME, System.currentTimeMillis()); + model.addAttribute(BUILDS, buildService.getAllBuilds()); + return "builds"; + } + + /** + * Handles the viewing of a specific build by its ID. + * If the build does not exist, it redirects to the builds page. + */ + @GetMapping("/build/{id}") + public String viewBuild(Model model, @PathVariable long id) { + model.addAttribute(TIME, System.currentTimeMillis()); + var build = buildService.getBuildById(id); + if (build == null) { + return "redirect:/builds"; + } + model.addAttribute(BUILD, build); + model.addAttribute(BUILD_INFO, buildService.getBuildInfo(build)); + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); + return "build_view"; + } + + /** + * Handles the creation of a new build. + */ + @PostMapping("/create_build") + public String createBuild(Model model, @RequestBody MultiValueMap formData) { + model.addAttribute(TIME, System.currentTimeMillis()); + var build = buildService.createBuild(formData.getFirst("name"), formData.getFirst("description")); + return "redirect:/build/" + build.getId(); + } + + /** + * Deletes a build by its ID. + */ + @GetMapping("/delete_build/{id}") + public String deleteBuild(Model model, @PathVariable long id) { + model.addAttribute(TIME, System.currentTimeMillis()); + buildService.deleteBuild(id); + return "redirect:/builds"; + } + /** * Handles the view of an asset by its QR code. * If the asset does not exist, it redirects to the index page. @@ -227,6 +280,7 @@ public class WebController { model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(assetType)); model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(assetType)); model.addAttribute(INPUT_LIST, assetService.getInputList(assetType)); + model.addAttribute(BUILDS, buildService.getAllBuilds()); return "create_asset"; } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java index ce59f3f..1cde430 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java @@ -99,6 +99,19 @@ public class AssetDescriptor { return String.format("%s-%s", type, property.getName()); } + /** + * Gets the property with the specified name. + * + * @param name The name of the property to retrieve. + * @return The AssetProperty with the given name. + */ + public AssetProperty getProperty(String name) { + return properties.stream() + .filter(property -> property.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No property found with name: " + name)); + } + /** * Creates a new instance of the asset type described by this descriptor. * diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java index fce5d91..293af72 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java @@ -24,6 +24,37 @@ public class AssetDescriptors { assets.add(property); } + /** + * Gets the descriptor for a specific asset type. + * + * @param type The type of the asset to retrieve the descriptor for. + */ + public AssetDescriptor getDescriptorForType(String type) { + return assets.stream() + .filter(assetDescriptor -> assetDescriptor.getType().equals(type)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No asset descriptor found for type: " + type)); + } + + /** + * Gets the property for a specific asset type and property name. + * + * @param type The type of the asset to retrieve the property for. + * @param propertyName The name of the property to retrieve. + */ + public AssetProperty getPropertyForType(String type, String propertyName) { + return getDescriptorForType(type).getProperty(propertyName); + } + + /** + * Gets the generic property for a specific property name. + * + * @param propertyName The name of the property to retrieve. + */ + public AssetProperty getGenericProperty(String propertyName) { + return getPropertyForType("asset", propertyName); + } + @Override public String toString() { var builder = new StringBuilder(); diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java index b137ec9..f657340 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -183,6 +183,9 @@ public class AssetProperty { return value.toString(); } else if (type == PropertyType.CAPACITY) { return convertCapacity((Long) value).toString(); + } else if (type == PropertyType.BUILD) { + var build = (Build) value; + return build.getName(); } else if (type.isEnum) { if (value instanceof AssetEnum assetEnum) { return assetEnum.getDisplayName(); diff --git a/src/main/java/be/seeseepuff/pcinv/models/Build.java b/src/main/java/be/seeseepuff/pcinv/models/Build.java index 5a6d74f..bd6893b 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/Build.java +++ b/src/main/java/be/seeseepuff/pcinv/models/Build.java @@ -21,10 +21,30 @@ public class Build @GeneratedValue private long id; + /** + * Indicates whether this build is a meta build. + * A meta build is a build that does not represent a physical computer, + * but rather a collection of parts that can be used in other builds. + * + * It is used internally to represents parts that are explicitly not part of a build. + */ + private boolean meta; + + /** + * The name of the build. + */ private String name; + /** + * A description of the build. + * This can be used to provide additional information about the build. + */ private String description; - @OneToMany(cascade = CascadeType.ALL) + /** + * A list of parts that are included in the build. + */ + @OneToMany(mappedBy = "build") + @OrderBy("type, brand, model, qr") private List parts; } diff --git a/src/main/java/be/seeseepuff/pcinv/models/BuildInfo.java b/src/main/java/be/seeseepuff/pcinv/models/BuildInfo.java new file mode 100644 index 0000000..a15a1e3 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/BuildInfo.java @@ -0,0 +1,20 @@ +package be.seeseepuff.pcinv.models; + +import be.seeseepuff.pcinv.meta.CapacityInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BuildInfo { + private long totalRam; + + /** + * Calculates the total RAM capacity of the build. + * + * @return A CapacityInfo object representing the total RAM capacity. + */ + public CapacityInfo getTotalRamCapacity() { + return CapacityInfo.of(totalRam); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/BuildRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/BuildRepository.java index cb895c3..957a45e 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/BuildRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/BuildRepository.java @@ -4,7 +4,14 @@ import be.seeseepuff.pcinv.models.Build; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface BuildRepository extends JpaRepository { + Build getBuildByNameAndMeta(String name, boolean meta); + + Build getBuildById(long id); + + List findAllByMeta(boolean meta); } diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index b592a4a..1ceb2cb 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -2,11 +2,9 @@ package be.seeseepuff.pcinv.services; import be.seeseepuff.pcinv.meta.*; import be.seeseepuff.pcinv.models.Asset; -import be.seeseepuff.pcinv.models.Build; import be.seeseepuff.pcinv.models.GenericAsset; import be.seeseepuff.pcinv.models.WorkLogEntry; import be.seeseepuff.pcinv.repositories.AssetRepository; -import be.seeseepuff.pcinv.repositories.BuildRepository; import be.seeseepuff.pcinv.repositories.GenericAssetRepository; import be.seeseepuff.pcinv.repositories.WorkLogRepository; import jakarta.persistence.EntityManager; @@ -30,6 +28,7 @@ public class AssetService { private final WorkLogRepository workLogRepository; private final Collection> repositories; private final EntityManager entityManager; + private final BuildService buildService; /** * Returns the count of all assets in the repository. @@ -253,6 +252,12 @@ public class AssetService { case "false" -> false; default -> null; }; + } else if (property.getType() == PropertyType.BUILD) { + var build = buildService.getBuildById(Integer.parseInt(stringValue)); + if (build == null) { + throw new IllegalArgumentException("Invalid build ID for property '" + property.getName() + "': " + stringValue); + } + return build; } else if (property.getType().isEnum) { for (var option : property.getOptions()) { if (option.getValue().equals(stringValue)) { diff --git a/src/main/java/be/seeseepuff/pcinv/services/BuildService.java b/src/main/java/be/seeseepuff/pcinv/services/BuildService.java index bef09c6..e216bbe 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/BuildService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/BuildService.java @@ -1,11 +1,14 @@ package be.seeseepuff.pcinv.services; import be.seeseepuff.pcinv.models.Build; +import be.seeseepuff.pcinv.models.BuildInfo; import be.seeseepuff.pcinv.repositories.BuildRepository; +import be.seeseepuff.pcinv.repositories.RamRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.ArrayList; import java.util.List; /** @@ -13,14 +16,22 @@ import java.util.List; */ @Service @RequiredArgsConstructor -public class BuildService -{ +public class BuildService { private final BuildRepository buildRepository; - public static final Build EMPTY = Build.builder() - .id(0) - .name("(None)") - .description("A meta build used to indicate that the part is not being used by anything") - .build(); + private final RamRepository ramRepository; + + @PostConstruct + private void init() { + Build empty = buildRepository.getBuildByNameAndMeta("None", true); + if (empty == null) { + empty = Build.builder() + .name("None") + .meta(true) + .build(); + } + empty.setDescription("A meta build to hold unused parts."); + empty = buildRepository.save(empty); + } /** * Gets a list of all computer builds, including meta builds. @@ -28,10 +39,7 @@ public class BuildService * @return A list of all builds. */ public List getAllBuilds() { - var build = new ArrayList(); - build.add(EMPTY); - build.addAll(getAllRealBuilds()); - return build; + return buildRepository.findAll(); } /** @@ -40,6 +48,66 @@ public class BuildService * @return A list of all builds. */ public List getAllRealBuilds() { - return buildRepository.findAll(); + return buildRepository.findAllByMeta(false); + } + + /** + * Gets a build by its ID. + * + * @param id The ID of the build to retrieve. + * @return The build with the given ID, or null if not found. + */ + public Build getBuildById(long id) { + return buildRepository.getBuildById(id); + } + + /** + * Creates a new build with the given name and description. + * + * @param name The name of the build. + * @param description The description of the build. + * @return The created build. + */ + @Transactional + public Build createBuild(String name, String description) { + Build build = Build.builder() + .name(name) + .description(description) + .build(); + return buildRepository.saveAndFlush(build); + } + + /** + * Deletes a build by its ID. + * + * @param id The ID of the build to delete. + */ + @Transactional + public void deleteBuild(long id) { + Build build = buildRepository.getBuildById(id); + for (var part : build.getParts()) { + part.setBuild(null); + } + buildRepository.deleteById(id); + } + + /** + * Gets the build information for a given build ID. + * + * @param build The build object for which to retrieve the information. + * @return The BuildInfo object containing the build information. + * @throws IllegalArgumentException if the build with the given ID does not exist. + */ + public BuildInfo getBuildInfo(Build build) { + var buildInfo = new BuildInfo(); + for (var part : build.getParts()) { + if (part.getType().equals("ram")) { + var asset = ramRepository.findByAsset(part); + if (asset.getCapacity() != null) { + buildInfo.setTotalRam(buildInfo.getTotalRam() + asset.getCapacity()); + } + } + } + return buildInfo; } } diff --git a/src/main/resources/templates/build_view.html b/src/main/resources/templates/build_view.html new file mode 100644 index 0000000..6d0c816 --- /dev/null +++ b/src/main/resources/templates/build_view.html @@ -0,0 +1,24 @@ + +
+
    +
  • Name:
  • +
  • Description:
  • +
  • Part Count:
  • +
  • Total RAM:
  • +
+ + + + + + + + + + + + + +
QRTypeBrandModel
+
+ diff --git a/src/main/resources/templates/builds.html b/src/main/resources/templates/builds.html new file mode 100644 index 0000000..877b2b8 --- /dev/null +++ b/src/main/resources/templates/builds.html @@ -0,0 +1,28 @@ + +
+ + + + + + + + + + + + + + + + + + + + + +
NameDescriptionPart CountActions
+ Delete +
+
+ diff --git a/src/main/resources/templates/view.html b/src/main/resources/templates/view.html index 655878c..241fa4d 100644 --- a/src/main/resources/templates/view.html +++ b/src/main/resources/templates/view.html @@ -7,7 +7,8 @@ - + +