Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions backend/pcmod/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package es.codeurjcstudents.pcmod.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;

import es.codeurjcstudents.pcmod.dto.ComponentDTO;
import es.codeurjcstudents.pcmod.service.ComponentsService;
Expand All @@ -30,4 +36,13 @@ public List<ComponentDTO> findTop3MoreRecent() {
return componentService.findTop3MoreRecent().stream().map(componentService::toDTO).toList();
}

@GetMapping("/{id}/image")
public ResponseEntity<Resource> getComponentImage(@PathVariable long id) throws SQLException {

Resource imageFile = componentService.getImageFile(id);
MediaType mediaType = MediaTypeFactory.getMediaType(imageFile).orElse(MediaType.IMAGE_JPEG);

return ResponseEntity.ok().contentType(mediaType).body(imageFile);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public record ComponentDTO(
String type,
String brand,
BigDecimal price,
Integer stock) {
Integer stock,
ImageDTO image) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

Expand All @@ -21,6 +22,9 @@ public class Component {
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id = null;

@OneToOne
private Image image;

@NotBlank(message = "El nombre del componente es obligatorio")
private String name;

Expand Down Expand Up @@ -62,6 +66,14 @@ public void setId(Long id) {
this.id = id;
}

public Image getImage() {
return image;
}

public void setImage(Image image) {
this.image = image;
}

public String getName() {
return name;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package es.codeurjcstudents.pcmod.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import es.codeurjcstudents.pcmod.model.Component;
import es.codeurjcstudents.pcmod.model.Image;

public interface ComponentsRepository extends JpaRepository<Component, Long> {

List<Component> findTop3ByOrderByIdDesc();

@Query("SELECT c.image from Component c WHERE c.id = :id")
Optional<Image> findImageById(long id);

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package es.codeurjcstudents.pcmod.service;

import java.sql.SQLException;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import es.codeurjcstudents.pcmod.dto.ComponentDTO;
import es.codeurjcstudents.pcmod.dto.ComponentMapper;
import es.codeurjcstudents.pcmod.model.Component;
import es.codeurjcstudents.pcmod.model.Image;
import es.codeurjcstudents.pcmod.repository.ComponentsRepository;

@Service
Expand All @@ -35,4 +40,15 @@ public ComponentDTO toDTO(Component component) {
public List<Component> findTop3MoreRecent() {
return componentRepository.findTop3ByOrderByIdDesc();
}

public Resource getImageFile(long id) throws SQLException {

Optional<Image> imageOptional = componentRepository.findImageById(id);

if (imageOptional.isPresent() && imageOptional.get().getImageFile() != null) {
return new InputStreamResource(imageOptional.get().getImageFile().getBinaryStream());
}
return null;

}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package es.codeurjcstudents.pcmod.service;

import java.io.IOException;
import java.math.BigDecimal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import es.codeurjcstudents.pcmod.enums.ComponentType;
import es.codeurjcstudents.pcmod.model.Component;
import es.codeurjcstudents.pcmod.model.User;
import es.codeurjcstudents.pcmod.model.Image;
import es.codeurjcstudents.pcmod.repository.ComponentsRepository;
import jakarta.annotation.PostConstruct;

Expand All @@ -31,41 +35,49 @@ public class DatabaseInitializer {
private ImageService imageService;

@PostConstruct
public void init() {
public void init() throws IOException {

// Sample Components

componentsRepository.save(new Component("AMD Ryzen 7 7800X3D",
Component component1 = new Component("AMD Ryzen 7 7800X3D",
"8 núcleos y 16 hilos ideales para gaming\r\n" + //
"96 MB de caché L3 3D V-Cache, baja latencia\r\n" + //
"Compatible con memorias DDR5, doble canal\r\n" + //
"Frecuencia base 4.2 GHz y turbo hasta 5.0 GHz",
ComponentType.CPU, "AMD", BigDecimal.valueOf(350.00), 5));
ComponentType.CPU, "AMD", BigDecimal.valueOf(350.00), 5);
setComponentImage(component1, "/sample_images/ryzen-7.webp");
componentsRepository.save(component1);

componentsRepository.save(new Component("NVIDIA GeForce RTX 5060 WINDFORCE MAX OC",
Component component2 = new Component("NVIDIA GeForce RTX 5060 WINDFORCE MAX OC",
"Ray Tracing y DLSS 4 para gaming fluido\r\n" + //
"8GB GDDR7 y arquitectura Blackwell\r\n" + //
"WINDFORCE 2X: refrigeración eficiente y silenciosa\r\n" + //
"DisplayPort 2.1 y HDMI 2.1b hasta 8K\r\n" + //
"Tensor Cores 5ª gen y Reflex 2 para IA avanzada",
ComponentType.GPU, "Gigabyte", BigDecimal.valueOf(359.85), 2));
ComponentType.GPU, "Gigabyte", BigDecimal.valueOf(359.85), 2);
setComponentImage(component2, "/sample_images/rtx-5060.jpg");
componentsRepository.save(component2);

componentsRepository.save(new Component("Kingston NV3",
Component component3 = new Component("Kingston NV3",
"Capacidad: 1 TB\r\n" + //
"Factor de forma de disco SSD: M.2\r\n" + //
"Interfaz: PCI Express 4.0\r\n" + //
"NVMe: Si\r\n" + //
"Tipo de memoria: 3D NAND",
ComponentType.STORAGE, "Kingston", BigDecimal.valueOf(149.95), 15));
ComponentType.STORAGE, "Kingston", BigDecimal.valueOf(149.95), 15);
setComponentImage(component3, "/sample_images/kingston-nv3.webp");
componentsRepository.save(component3);

componentsRepository.save(new Component("Corsair Vengeance LPX 2X8GB",
Component component4 = new Component("Corsair Vengeance LPX 2X8GB",
"Latencia CAS: 16\r\n" + //
"Memoria interna: 16 GB\r\n" + //
"Tipo de memoria interna: DDR4\r\n" + //
"Velocidad de memoria del reloj: 3200 MHz",
ComponentType.RAM, "Corsair", BigDecimal.valueOf(149.95), 2));
ComponentType.RAM, "Corsair", BigDecimal.valueOf(149.95), 2);
setComponentImage(component4, "/sample_images/corsair-vengeance.webp");
componentsRepository.save(component4);

componentsRepository.save(new Component("MAG A650BN",
Component component5 = new Component("MAG A650BN",
"Potencia total: 650 W\r\n" + //
"Voltaje de entrada AC: 100 - 240 V\r\n" + //
"Frecuencia de entrada AC: 50/60 Hz\r\n" + //
Expand All @@ -76,9 +88,11 @@ public void init() {
"Corriente máxima de salida (+12V): 54 A\r\n" + //
"Corriente máxima de salida (+5Vsb): 20 A\r\n" + //
"Funciones de protección de poder: Sobreintensidad, Sobretensión, Sobrevoltaje, Cortocircuito",
ComponentType.PSU, "MSI", BigDecimal.valueOf(55.90), 20));
ComponentType.PSU, "MSI", BigDecimal.valueOf(55.90), 20);
setComponentImage(component5, "/sample_images/mag-a650bn.webp");
componentsRepository.save(component5);

componentsRepository.save(new Component("Asus TUF GAMING B550M-PLUS WIFI II",
Component component6 = new Component("Asus TUF GAMING B550M-PLUS WIFI II",
"Fabricante de procesador: AMD\r\n" + //
"Socket de procesador: Zócalo AM4\r\n" + //
"Procesador compatible: AMD Ryzen 3, AMD Ryzen 5, AMD Ryzen 7, 3rd Generation AMD Ryzen 9, AMD Ryzen 9 5th Gen\r\n"
Expand All @@ -88,9 +102,11 @@ public void init() {
"Número de ranuras de memoria: 4\r\n" + //
"Tipo de ranuras de memoria: DIMM\r\n" + //
"Canales de memoria: Dual-channel",
ComponentType.MOTHERBOARD, "Asus", BigDecimal.valueOf(118.90), 15));
ComponentType.MOTHERBOARD, "Asus", BigDecimal.valueOf(118.90), 15);
setComponentImage(component6, "/sample_images/asus-tuf-b550m.webp");
componentsRepository.save(component6);

componentsRepository.save(new Component("Lian Li Vector V100",
Component component7 = new Component("Lian Li Vector V100",
"Formato: ATX (Midi Tower), compatibilidad total con EATX, Micro ATX y Mini-ITX\r\n" + //
"Materiales: Acero, Vidrio templado, Plástico. Acabado negro profesional y paneles transparentes\r\n" + //
"Bahías de almacenamiento: 1 x 3.5\", 1 x 2.5\" (total 2 unidades soportadas)\r\n" + //
Expand All @@ -101,43 +117,54 @@ public void init() {
"Iluminación: ARGB multi, completamente personalizable\r\n" + //
"Conectividad frontal: 2 x USB 3.0, 1 x USB Type-C, combo audio/micrófono\r\n" + //
"Peso aproximado: 14,38 kg (volumétrico)",
ComponentType.CASE, "Lian Li", BigDecimal.valueOf(65.99), 5));
ComponentType.CASE, "Lian Li", BigDecimal.valueOf(65.99), 5);
setComponentImage(component7, "/sample_images/lian-li-v100.webp");
componentsRepository.save(component7);

componentsRepository.save(new Component("Intel Core i5-12400F",
Component component8 = new Component("Intel Core i5-12400F",
"6 núcleos de alto rendimiento con 12 hilos\r\n" + //
"Frecuencias de 2.5 a 4.4GHz para gaming avanzado\r\n" + //
"Compatible con memorias DDR4 y DDR5 hasta 128GB\r\n" + //
"Incluye refrigerador en caja y arquitectura Alder Lake\r\n" + //
"PCIe 5.0 y 4.0 para GPUs/SSDs de nueva generación\r\n" + //
"No incluye gráficos integrados, necesita gráfica dedicada",
ComponentType.CPU, "Intel",
BigDecimal.valueOf(153.97), 14));
BigDecimal.valueOf(153.97), 14);
setComponentImage(component8, "/sample_images/i5-12400f.webp");
componentsRepository.save(component8);

componentsRepository.save(new Component("AMD Radeon RX 9060 XT DUAL WHITE",
Component component9 = new Component("AMD Radeon RX 9060 XT DUAL WHITE",
"16GB GDDR6 para gaming y creación profesional\r\n" + //
"PCIe 5.0 y diseño blanco compacto\r\n" + //
"Refrigeración dual ASUS Axial-tech silenciosa\r\n" + //
"Soporta hasta 3 monitores simultáneos\r\n" + //
"Frecuencias OC hasta 3 250 MHz",
ComponentType.GPU, "Asus", BigDecimal.valueOf(482.90), 5));
ComponentType.GPU, "Asus", BigDecimal.valueOf(482.90), 5);
setComponentImage(component9, "/sample_images/rx-9060.jpg");
componentsRepository.save(component9);

componentsRepository.save(new Component("Seagate BarraCuda 3.5",
Component component10 = new Component("Seagate BarraCuda 3.5",
"Capacidad de 2 TB para grandes volúmenes de datos\r\n" + //
"Velocidad de 7200 rpm para acceso rápido\r\n" + //
"Interfaz SATA III de 6 Gb/s\r\n" + //
"Buffer de 256 MB optimiza el rendimiento\r\n" + //
"Compatible con PC de sobremesa y estaciones de trabajo\r\n" + //
"Alta fiabilidad y durabilidad para uso intensivo",
ComponentType.STORAGE, "Seagate", BigDecimal.valueOf(121.99), 1));
ComponentType.STORAGE, "Seagate", BigDecimal.valueOf(121.99), 1);
setComponentImage(component10, "/sample_images/seagate-barracuda.webp");
componentsRepository.save(component10);

componentsRepository.save(new Component("Kingston FURY Beast",
Component component11 = new Component("Kingston FURY Beast",
"Latencia CAS: 36\r\n" + //
"Memoria interna: 16 GB\r\n" + //
"Diseño de memoria (módulos x tamaño): 2 x 8 GB\r\n" + //
"Tipo de memoria interna: DDR5\r\n" + //
"Velocidad de memoria del reloj: 6000 MHz",
ComponentType.RAM, "Kingston", BigDecimal.valueOf(269.00), 15));
ComponentType.RAM, "Kingston", BigDecimal.valueOf(269.00), 15);
setComponentImage(component11, "/sample_images/kingston-fury.jpg");
componentsRepository.save(component11);

// Sample Users
userService.createUser(new User("user", "example", "user_example", "c/example_address 1", "user@example.com",
userPasswordEncoded, "REGISTERED_USER"));
userService.createUser(
Expand All @@ -146,4 +173,11 @@ public void init() {

}

// Auxiliar functions
public void setComponentImage(Component sampleComponent, String classpathResource) throws IOException {

Resource image = new ClassPathResource(classpathResource);
Image createdImage = imageService.createImage(image.getInputStream());
sampleComponent.setImage(createdImage);
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;

import java.io.IOException;
import java.io.InputStream;

@Tag("server-system")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Expand Down Expand Up @@ -47,4 +51,19 @@ public void getComponents() {
.body("content[0].stock", equalTo(5));
}

@Test
public void getComponentImage() throws IOException {
byte[] expectedImage;
try (InputStream imageResource = getClass().getResourceAsStream("/sample_images/ryzen-7.webp")) {
expectedImage = imageResource.readAllBytes();
}

byte[] actualImage = given().header("Content-Type", "application/json")
.when().get("/components/1/image")
.then().statusCode(200).contentType("image/jpeg")
.extract().asByteArray();

assertArrayEquals(expectedImage, actualImage);
}

}
Loading
Loading