Commit 98238240 by Rubén Ramírez

feat: [Capitulo]: Añadidos métodos para obtener y trabajar con capítulos

parent 1f435592
......@@ -49,9 +49,11 @@ public class Recurso {
private Set<Genero> generos = new HashSet<>();
// Relación con Capítulo; un recurso tiene varios capítulos
@OneToMany(mappedBy = "recurso")
private List<Capitulo> capitulos = new ArrayList<>();
@OneToMany(mappedBy = "recurso", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Capitulo> capitulosManga = new ArrayList<>();
@OneToMany(mappedBy = "recurso", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Capitulo> capitulosAnime = new ArrayList<>();
public Recurso(String titulo, String descripcion, LocalDate fechaPublicacion, String autor, String fotoUrl) {
this.titulo = titulo;
......
......@@ -70,6 +70,9 @@ public class RepositorioRecurso {
@Transactional
public void borrarRecurso(Recurso recurso) {
recurso = em.merge(recurso);
recurso.getCapitulosManga().clear();
recurso.getCapitulosAnime().clear();
em.flush();
em.remove(recurso);
}
......@@ -80,8 +83,6 @@ public class RepositorioRecurso {
@Transactional(readOnly = true)
public List<Recurso> listarRecursos() {
return em.createQuery("SELECT r FROM Recurso r", Recurso.class)
.setMaxResults(15)
.getResultList();
return em.createQuery("SELECT r FROM Recurso r", Recurso.class).getResultList();
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ package com.ujaen.tfg.mangaffinity.rest;
import com.ujaen.tfg.mangaffinity.entidades.Capitulo;
import com.ujaen.tfg.mangaffinity.entidades.Genero;
import com.ujaen.tfg.mangaffinity.entidades.Recurso;
import com.ujaen.tfg.mangaffinity.excepciones.CapituloNoExiste;
import com.ujaen.tfg.mangaffinity.excepciones.RecursoNoExiste;
import com.ujaen.tfg.mangaffinity.rest.DTO.DTOCapitulo;
import com.ujaen.tfg.mangaffinity.rest.DTO.DTORecurso;
......@@ -32,12 +33,23 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDate;
import java.util.UUID;
import java.util.UUID;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@RestController
@RequestMapping("/recursos")
public class RecursosController {
@Autowired
private ServicioRecursos servicioRecursos;
@Autowired
......@@ -49,20 +61,26 @@ public class RecursosController {
@RequestPart(value = "foto", required = false) MultipartFile foto) {
try {
String fotoUrl = null;
String fileName = null;
// Verificar tipo de archivo (solo imágenes)
// Verificar si se adjuntó una imagen y si es un tipo válido
if (foto != null && !foto.isEmpty()) {
String mimeType = foto.getContentType();
if (mimeType == null || (!mimeType.equals("image/jpeg") && !mimeType.equals("image/png"))) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Solo se permiten imágenes JPEG o PNG.");
}
// Renombrar el archivo con un nombre único
String fileName = UUID.randomUUID().toString() + "_" + foto.getOriginalFilename();
Path uploadsPath = Paths.get("uploads/");
// Obtener el nombre original y limpiar el nombre del archivo
String originalFileName = foto.getOriginalFilename();
if (originalFileName != null) {
// Reemplazar espacios y caracteres especiales en el nombre
fileName = originalFileName.replaceAll("\\s+", "_").replaceAll("[^a-zA-Z0-9._-]", "");
} else {
fileName = "imagen_predeterminada.png";
}
// Crear directorio si no existe
// Ruta de almacenamiento
Path uploadsPath = Paths.get("uploads/");
if (!Files.exists(uploadsPath)) {
Files.createDirectories(uploadsPath);
}
......@@ -71,32 +89,30 @@ public class RecursosController {
Path filePath = uploadsPath.resolve(fileName);
Files.copy(foto.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// Crear la URL completa de la imagen
String urlBase = "http://localhost:8080";
fotoUrl = urlBase + "/uploads/" + fileName.replace(" ", "_");
}
// Crear el recurso con la imagen (si hay)
// Crear el recurso con solo el nombre del archivo en la BD
Recurso nuevoRecurso = new Recurso(
recursoDTO.getTitulo(),
recursoDTO.getDescripcion(),
recursoDTO.getFechaPublicacion(),
recursoDTO.getAutor(),
fotoUrl
fileName // Guardamos solo el nombre del archivo
);
nuevoRecurso.setGeneros(recursoDTO.getGeneros());
servicioRecursos.crearRecurso(nuevoRecurso);
return ResponseEntity.status(HttpStatus.CREATED).body("Recurso creado correctamente");
return ResponseEntity.status(HttpStatus.CREATED).build();
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al subir la imagen");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/titulo/{titulo}")
public ResponseEntity<List<DTORecurso>> buscarPorTitulo(@PathVariable String titulo) {
try {
......@@ -165,6 +181,7 @@ public class RecursosController {
@DeleteMapping("/{id}")
public ResponseEntity<Void> borrarRecurso(@PathVariable Long id) {
try {
......@@ -178,37 +195,84 @@ public class RecursosController {
if (recurso.getFotoUrl() != null && !recurso.getFotoUrl().isEmpty()) {
Path imagePath = Paths.get("uploads/", recurso.getFotoUrl().replace("/uploads/", ""));
// Eliminar la imagen asociada si existe
// Eliminar l imagen si existe
if (Files.exists(imagePath)) {
try {
Files.delete(imagePath); // Eliminar la imagen
Files.delete(imagePath);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
// Eliminar el recurso de la base de datos
servicioRecursos.borrarRecurso(id);
// Responder con éxito
return ResponseEntity.noContent().build();
} catch (SecurityException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // En caso de que el usuario no tenga permiso
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); // Error inesperado
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PutMapping("/{id}")
public ResponseEntity<DTORecurso> modificarRecurso(@PathVariable Long id, @RequestBody DTORecurso dtoRecurso) {
@PutMapping(value = "/{id}", consumes = {"multipart/form-data"})
public ResponseEntity<DTORecurso> modificarRecurso(
@PathVariable Long id,
@RequestPart("recurso") DTORecurso dtoRecurso,
@RequestPart(value = "foto", required = false) MultipartFile foto) {
try {
Recurso nuevosDatos = mapper.entity(dtoRecurso);
Recurso recursoModificado = servicioRecursos.modificarRecurso(id, nuevosDatos);
Recurso recursoExistente = servicioRecursos.buscarRecursoPorId(id);
if (recursoExistente == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
String fileName = recursoExistente.getFotoUrl(); // Mantener la imagen actual por defecto
// Si se adjunta una nueva imagen, la procesamos
if (foto != null && !foto.isEmpty()) {
String mimeType = foto.getContentType();
if (mimeType == null || (!mimeType.equals("image/jpeg") && !mimeType.equals("image/png"))) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
// Eliminar la imagen anterior si existe
if (fileName != null && !fileName.isEmpty()) {
Path oldFilePath = Paths.get("uploads/", fileName);
if (Files.exists(oldFilePath)) {
Files.delete(oldFilePath);
}
}
// Guardar la nueva imagen
String originalFileName = foto.getOriginalFilename();
if (originalFileName != null) {
fileName = originalFileName.replaceAll("\\s+", "_").replaceAll("[^a-zA-Z0-9._-]", "");
}
Path uploadsPath = Paths.get("uploads/");
if (!Files.exists(uploadsPath)) {
Files.createDirectories(uploadsPath);
}
Path filePath = uploadsPath.resolve(fileName);
Files.copy(foto.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
}
// Actualizar los datos del recurso
recursoExistente.setTitulo(dtoRecurso.getTitulo());
recursoExistente.setDescripcion(dtoRecurso.getDescripcion());
recursoExistente.setFechaPublicacion(dtoRecurso.getFechaPublicacion());
recursoExistente.setAutor(dtoRecurso.getAutor());
recursoExistente.setGeneros(dtoRecurso.getGeneros()); // Asignar correctamente los géneros
recursoExistente.setFotoUrl(fileName); // Asignar nueva imagen (o mantener la actual)
// Guardar cambios en la BD
Recurso recursoModificado = servicioRecursos.modificarRecurso(id, recursoExistente);
return ResponseEntity.ok(mapper.dto(recursoModificado));
} catch (RecursoNoExiste e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
} catch (SecurityException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} catch (Exception e) {
......@@ -231,13 +295,16 @@ public class RecursosController {
@PostMapping("/{id}/capitulos")
public ResponseEntity<String> anadirCapitulo(@PathVariable Long id, @RequestBody DTOCapitulo dtoCapitulo) {
try {
Capitulo nuevoCapitulo = mapper.entity(dtoCapitulo, servicioRecursos.buscarRecursoPorId(id));
Recurso recurso = servicioRecursos.buscarRecursoPorId(id);
if (recurso == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
Capitulo nuevoCapitulo = mapper.entity(dtoCapitulo, recurso);
servicioRecursos.anadirCapitulo(id, nuevoCapitulo);
return ResponseEntity.status(HttpStatus.CREATED).build();
} catch (RecursoNoExiste e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("El recurso no existe.");
} catch (SecurityException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
......@@ -247,13 +314,86 @@ public class RecursosController {
public ResponseEntity<List<DTOCapitulo>> obtenerCapitulosDeRecurso(@PathVariable Long id) {
try {
List<Capitulo> capitulos = servicioRecursos.obtenerCapitulosDeRecurso(id);
if (capitulos.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
List<DTOCapitulo> dtoCapitulos = capitulos.stream().map(mapper::dto).toList();
return ResponseEntity.ok(dtoCapitulos);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PutMapping("/{recursoId}/capitulos/{id}")
public ResponseEntity<Void> modificarCapitulo(@PathVariable Long id, @RequestBody DTOCapitulo dtoCapitulo) {
try {
if (!id.equals(dtoCapitulo.getId())) {
return ResponseEntity.badRequest().build();
}
Capitulo capituloExistente = servicioRecursos.buscarCapituloPorId(id);
if (capituloExistente == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
Capitulo capituloModificado = mapper.entity(dtoCapitulo, capituloExistente.getRecurso());
capituloModificado.setId(id);
servicioRecursos.modificarCapitulo(capituloModificado);
return ResponseEntity.noContent().build();
} catch (CapituloNoExiste e) {
return ResponseEntity.notFound().build();
} catch (IllegalStateException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
} catch (SecurityException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/{recursoId}/capitulos/{capituloId}")
public ResponseEntity<DTOCapitulo> obtenerCapituloPorId(
@PathVariable Long recursoId,
@PathVariable Long capituloId) {
try {
// Verificar si el recurso existe
Recurso recurso = servicioRecursos.buscarRecursoPorId(recursoId);
if (recurso == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.build(); // Recurso no encontrado
}
// Buscar el capítulo dentro del recurso
Capitulo capitulo = servicioRecursos.buscarCapituloPorId(capituloId);
if (capitulo == null || !capitulo.getRecurso().getId().equals(recursoId)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.build(); // Capítulo no encontrado o no pertenece al recurso
}
// Convertir a DTO y devolver
return ResponseEntity.ok(mapper.dto(capitulo));
} catch (Exception e) {
// Loguear el error para diagnóstico
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); // Error interno del servidor
}
}
@GetMapping("/generos")
public ResponseEntity<List<String>> obtenerGeneros() {
try {
List<String> generos = servicioRecursos.obtenerGeneros();
return ResponseEntity.ok(generos);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
\ No newline at end of file
......@@ -47,6 +47,7 @@ public class ServicioSeguridad {
.requestMatchers(HttpMethod.GET, "/recursos/genero/**").permitAll()
.requestMatchers(HttpMethod.GET, "/recursos/fecha").permitAll()
.requestMatchers(HttpMethod.GET, "/recursos/{id}").permitAll()
.requestMatchers(HttpMethod.GET, "/recursos/generos").permitAll()
.requestMatchers(HttpMethod.GET, "/recursos").permitAll()
.requestMatchers(HttpMethod.POST, "/recursos/").hasAuthority("ROLE_ADMIN")
.requestMatchers(HttpMethod.PUT, "/recursos/{id}").hasAuthority("ROLE_ADMIN")
......@@ -58,7 +59,10 @@ public class ServicioSeguridad {
.requestMatchers(HttpMethod.GET, "/biblioteca/{usuarioId}/recursos/categoria/{categoria}").authenticated()
.requestMatchers(HttpMethod.DELETE, "/biblioteca/{usuarioId}/recursos/{recursoId}").authenticated()
.requestMatchers(HttpMethod.PUT, "/biblioteca/{usuarioId}/recursos/{recursoId}/categoria").authenticated()
.anyRequest().authenticated() // Todo lo demás requiere autenticación
.requestMatchers(HttpMethod.GET, "/recursos/{recursoId}/capitulos/{capituloId}").permitAll()
.requestMatchers(HttpMethod.PUT, "/recursos/{recursoId}/capitulos/{capituloId}").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(new JwtFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class) // Usar solo JWT
.build();
......
package com.ujaen.tfg.mangaffinity.servicios;
import com.ujaen.tfg.mangaffinity.entidades.Capitulo;
import com.ujaen.tfg.mangaffinity.entidades.FuenteCapitulo;
import com.ujaen.tfg.mangaffinity.entidades.Genero;
import com.ujaen.tfg.mangaffinity.entidades.Recurso;
import com.ujaen.tfg.mangaffinity.entidades.*;
import com.ujaen.tfg.mangaffinity.excepciones.CapituloNoExiste;
import com.ujaen.tfg.mangaffinity.excepciones.RecursoNoExiste;
import com.ujaen.tfg.mangaffinity.repositorios.RepositorioCapitulo;
import com.ujaen.tfg.mangaffinity.repositorios.RepositorioRecurso;
......@@ -15,7 +13,11 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class ServicioRecursos {
@Autowired
......@@ -100,32 +102,120 @@ public class ServicioRecursos {
if (recurso == null) {
throw new RecursoNoExiste();
}
List<Capitulo> capitulos = repositorioCapitulo.obtenerCapitulosRecurso(recursoId);
if (capitulos.size() < 4) {
repositorioCapitulo.crearCapitulo(nuevoCapitulo);
List<Capitulo> listaCapitulos = (nuevoCapitulo.getTipo() == TipoRecurso.MANGA)
? recurso.getCapitulosManga()
: recurso.getCapitulosAnime();
// Asociar fuentes al capítulo
for (FuenteCapitulo fuente : nuevoCapitulo.getFuentes()) {
fuente.setCapitulo(nuevoCapitulo);
}
if (listaCapitulos.size() < 4) {
listaCapitulos.add(nuevoCapitulo);
} else {
Capitulo penultimo = capitulos.get(2);
Capitulo ultimo = capitulos.get(3);
Capitulo penultimo = listaCapitulos.get(2);
Capitulo ultimo = listaCapitulos.get(3);
List<FuenteCapitulo> nuevasFuentes = new ArrayList<>();
for (FuenteCapitulo fuente : ultimo.getFuentes()) {
nuevasFuentes.add(new FuenteCapitulo(fuente.getNombreFuente(), fuente.getUrlFuente()));
FuenteCapitulo nuevaFuente = new FuenteCapitulo(null, fuente.getNombreFuente(), fuente.getUrlFuente(), penultimo);
nuevasFuentes.add(nuevaFuente);
}
penultimo.setNumero(ultimo.getNumero());
penultimo.setTitulo(ultimo.getTitulo());
penultimo.setTipo(ultimo.getTipo());
penultimo.setFuentes(nuevasFuentes);
repositorioCapitulo.borrarCapitulo(ultimo);
repositorioCapitulo.actualizarCapitulo(penultimo);
repositorioCapitulo.crearCapitulo(nuevoCapitulo);
listaCapitulos.remove(ultimo);
listaCapitulos.add(nuevoCapitulo);
}
repositorioRecurso.merge(recurso);
}
@Transactional
public List<Capitulo> obtenerCapitulosDeRecurso(Long recursoId) {
List<Capitulo> capitulos = repositorioCapitulo.obtenerCapitulosRecurso(recursoId);
capitulos.forEach(capitulo -> capitulo.getFuentes().size());
return capitulos;
}
public List<String> obtenerGeneros() {
return Arrays.stream(Genero.values())
.map(Enum::name)
.collect(Collectors.toList());
}
@Transactional
public void modificarCapitulo(Capitulo capituloModificado) {
Capitulo capitulo = repositorioCapitulo.buscarPorId(capituloModificado.getId());
if (capitulo == null) {
throw new CapituloNoExiste();
}
// Asegurar que el capítulo pertenece a un recurso antes de modificarlo
if (capitulo.getRecurso() == null) {
throw new IllegalStateException("El capítulo no está asociado a ningún recurso.");
}
// Modificar solo si los valores no son nulos o inválidos
if (capituloModificado.getNumero() > 0) {
capitulo.setNumero(capituloModificado.getNumero());
}
if (capituloModificado.getTitulo() != null && !capituloModificado.getTitulo().isBlank()) {
capitulo.setTitulo(capituloModificado.getTitulo());
}
if (capituloModificado.getTipo() != null) {
capitulo.setTipo(capituloModificado.getTipo());
}
if (capituloModificado.getFuentes() != null) {
// Crear un mapa con las fuentes existentes para facilitar la comparación
Map<Long, FuenteCapitulo> fuentesExistentes = capitulo.getFuentes().stream()
.collect(Collectors.toMap(FuenteCapitulo::getId, f -> f));
List<FuenteCapitulo> nuevasFuentes = new ArrayList<>();
for (FuenteCapitulo fuenteNueva : capituloModificado.getFuentes()) {
if (fuenteNueva.getId() != null && fuentesExistentes.containsKey(fuenteNueva.getId())) {
// Si la fuente ya existe, actualizamos su información
FuenteCapitulo fuenteExistente = fuentesExistentes.get(fuenteNueva.getId());
fuenteExistente.setNombreFuente(fuenteNueva.getNombreFuente());
fuenteExistente.setUrlFuente(fuenteNueva.getUrlFuente());
nuevasFuentes.add(fuenteExistente);
} else {
// Si la fuente es nueva, la agregamos correctamente
fuenteNueva.setCapitulo(capitulo);
nuevasFuentes.add(fuenteNueva);
}
}
// Remover las fuentes que no están en la nueva lista
capitulo.getFuentes().clear();
capitulo.getFuentes().addAll(nuevasFuentes);
}
repositorioCapitulo.actualizarCapitulo(capitulo);
}
@Transactional(readOnly = true)
public Capitulo buscarCapituloPorId(Long id) {
Capitulo capitulo = repositorioCapitulo.buscarPorId(id);
if (capitulo == null) {
throw new CapituloNoExiste();
}
return capitulo;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment