From 2e4239408b26f9fe40e9bd4baa92363b57a38c68 Mon Sep 17 00:00:00 2001 From: andre00bejarano00vaca Date: Mon, 27 Oct 2025 15:48:54 -0400 Subject: [PATCH] incorporamos webflux, video en lotes, incorporamos controller webflux para lotes, separamos el aplicaction properties en prod y dev --- pom.xml | 4 + .../controller/LoteControllerWebFlux.java | 90 +++++++++++++++++++ .../example/fercoganbackend/entity/Lote.java | 9 ++ .../repository/LoteRepository.java | 11 ++- .../service/ContadorService.java | 2 +- .../service/LoteServiceWebFlux.java | 71 +++++++++++++++ src/main/resources/application-dev.properties | 3 + .../resources/application-prod.properties | 3 + src/main/resources/application.properties | 4 +- 9 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/example/fercoganbackend/controller/LoteControllerWebFlux.java create mode 100644 src/main/java/com/example/fercoganbackend/service/LoteServiceWebFlux.java create mode 100644 src/main/resources/application-dev.properties create mode 100644 src/main/resources/application-prod.properties diff --git a/pom.xml b/pom.xml index c8501a9..135fa04 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-webflux + org.springframework.security spring-security-test diff --git a/src/main/java/com/example/fercoganbackend/controller/LoteControllerWebFlux.java b/src/main/java/com/example/fercoganbackend/controller/LoteControllerWebFlux.java new file mode 100644 index 0000000..4fc4ae7 --- /dev/null +++ b/src/main/java/com/example/fercoganbackend/controller/LoteControllerWebFlux.java @@ -0,0 +1,90 @@ +package com.example.fercoganbackend.controller; + +import com.example.fercoganbackend.entity.Lote; +import com.example.fercoganbackend.service.LoteServiceWebFlux; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/webflux/lotes") +public class LoteControllerWebFlux { + + @Autowired + private LoteServiceWebFlux loteService; + + @GetMapping + public java.util.List getAll() { + return loteService.findAll(); + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id) { + return loteService.findById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public Lote create(@RequestBody Lote remate) { + return loteService.save(remate); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody Lote remate) { + return loteService.findById(id) + .map(r -> { + remate.setId(id); + Lote updated = loteService.save(remate); + return ResponseEntity.ok(updated); + }) + .orElse(ResponseEntity.notFound().build()); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + loteService.delete(id); + return ResponseEntity.noContent().build(); + } + + // -------------------- NUEVO: SSE por LOTE -------------------- + // Endpoint que devuelve el flujo de actualizaciones solo para ese lote (id). + @GetMapping(value = "/stream/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux streamLoteById(@PathVariable Long id) { + // flujo de futuras actualizaciones (todos los lotes, luego filtramos por id) + Flux updates = loteService.getSink().asFlux() + .filter(l -> l != null && l.getId() != null && l.getId().equals(id)); + + // enviamos primero el estado actual (si existe), luego las actualizaciones + Optional current = loteService.findById(id); + if (current.isPresent()) { + return Flux.concat(Flux.just(current.get()), updates); + } else { + // si no existe ahora, devolvemos solo futuras actualizaciones (por ejemplo creación posterior) + return updates; + } + } + + @GetMapping(value = "/stream/cabanaid/{cabanaId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux streamLote(@PathVariable Long cabanaId) { + // Flujo de futuras actualizaciones (todos los lotes, luego filtramos por cabanaId) + Flux updates = loteService.getSink().asFlux() + .filter(lote -> lote.getCabana().getId().equals(cabanaId)); + + // Enviamos primero el estado actual (todos los lotes de la cabaña), luego las actualizaciones + List currentLotes = loteService.findByCabanaId(cabanaId); + + if (!currentLotes.isEmpty()) { + return Flux.concat(Flux.fromIterable(currentLotes), updates); + } else { + // Si no existen lotes ahora, devolvemos solo futuras actualizaciones + return updates; + } + } +} + diff --git a/src/main/java/com/example/fercoganbackend/entity/Lote.java b/src/main/java/com/example/fercoganbackend/entity/Lote.java index e585c21..30f2b8d 100644 --- a/src/main/java/com/example/fercoganbackend/entity/Lote.java +++ b/src/main/java/com/example/fercoganbackend/entity/Lote.java @@ -17,6 +17,7 @@ public class Lote { private Double puja; private String estado; private Boolean visible = true; + private String video; @ManyToOne @JoinColumn(name = "remate_id") @@ -26,6 +27,14 @@ public class Lote { @JoinColumn(name = "cabana_id") private Cabana cabana; + public String getVideo() { + return video; + } + + public void setVideo(String video) { + this.video = video; + } + public Long getId() { return id; } diff --git a/src/main/java/com/example/fercoganbackend/repository/LoteRepository.java b/src/main/java/com/example/fercoganbackend/repository/LoteRepository.java index 4bfc70d..214c3e6 100644 --- a/src/main/java/com/example/fercoganbackend/repository/LoteRepository.java +++ b/src/main/java/com/example/fercoganbackend/repository/LoteRepository.java @@ -1,6 +1,15 @@ package com.example.fercoganbackend.repository; import com.example.fercoganbackend.entity.Lote; +import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaRepository; -public interface LoteRepository extends JpaRepository {} +import java.util.List; + +public interface LoteRepository extends JpaRepository { + // Con ordenamiento + //List findByCabanaIdOrderByNumeroLote(Long cabanaId); + + // Para paginación + List findByCabanaId(Long cabanaId); +} diff --git a/src/main/java/com/example/fercoganbackend/service/ContadorService.java b/src/main/java/com/example/fercoganbackend/service/ContadorService.java index dab4964..4687752 100644 --- a/src/main/java/com/example/fercoganbackend/service/ContadorService.java +++ b/src/main/java/com/example/fercoganbackend/service/ContadorService.java @@ -22,7 +22,7 @@ public class ContadorService { } else { incremento = 500; } - return contador.getAndAdd(incremento); + return contador.addAndGet(incremento); } diff --git a/src/main/java/com/example/fercoganbackend/service/LoteServiceWebFlux.java b/src/main/java/com/example/fercoganbackend/service/LoteServiceWebFlux.java new file mode 100644 index 0000000..cbda1b2 --- /dev/null +++ b/src/main/java/com/example/fercoganbackend/service/LoteServiceWebFlux.java @@ -0,0 +1,71 @@ +package com.example.fercoganbackend.service; + + +import com.example.fercoganbackend.entity.Lote; +import com.example.fercoganbackend.repository.LoteRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Sinks; + +import java.util.List; +import java.util.Optional; + +@Service +public class LoteServiceWebFlux { + + private final LoteRepository repo; + private final Sinks.Many sink; // canal que emite actualizaciones por lote + + public LoteServiceWebFlux(LoteRepository repo) { + this.repo = repo; + // replay().latest() guarda el último emitido (por si alguien se suscribe tarde) + this.sink = Sinks.many().replay().latest(); + } + + public List findAll() { + return repo.findAll(); + } + + public List findByCabanaId(Long id) { + return repo.findByCabanaId(id); + } + + public Optional findById(Long id) { + return repo.findById(id); + } + + @Transactional + public Lote save(Lote lote) { + Lote saved = repo.save(lote); + emitChange(saved); // notificamos a los suscriptores el lote actualizado + return saved; + } + + @Transactional + public void delete(Long id) { + // Intentamos obtener el lote antes de borrar para poder notificar su eliminación + Optional maybe = repo.findById(id); + repo.deleteById(id); + // Emitimos un "evento de eliminación": un Lote con id y visible = false + Lote tombstone = maybe.orElseGet(() -> { + Lote t = new Lote(); + t.setId(id); + return t; + }); + // Si tu entidad Lote tiene campo visible, marcalo; si no, sigue emitiendo el id. + try { + tombstone.setVisible(false); + } catch (Exception ignored) {} + emitChange(tombstone); + } + + private void emitChange(Lote lote) { + // emitimos el objeto lote al sink; los controladores filtran por id + sink.tryEmitNext(lote); + } + + public Sinks.Many getSink() { + return sink; + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..e1c1d4a --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/testdb +spring.datasource.username=andre +spring.datasource.password=andre \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..be9eba9 --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:mysql://192.168.5.250:3306/TEST_fercoApp +spring.datasource.username=ferco +spring.datasource.password=9+k+9076[5S26#C1mn"u \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1714341..1b1d6d5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,5 @@ spring.application.name=fercoganbackend -spring.datasource.url=jdbc:mysql://192.168.5.250:3306/TEST_fercoApp -spring.datasource.username=ferco -spring.datasource.password=9+k+9076[5S26#C1mn"u +spring.profiles.active=dev spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update