audios y podcast
This commit is contained in:
parent
8b1f59c7f3
commit
376fa3c5a6
|
|
@ -3,9 +3,22 @@ package es.tatvil.taiageweb;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Punto de entrada de la aplicación <strong>TAIage</strong>.
|
||||||
|
*
|
||||||
|
* <p>Plataforma web para la preparación de las oposiciones TAI (Técnico Auxiliar
|
||||||
|
* de Informática) de la Administración General del Estado (AGE). Ofrece acceso
|
||||||
|
* al temario completo, legislación y noticias relevantes, con control de acceso
|
||||||
|
* por roles y pasarela de pago integrada con Stripe.</p>
|
||||||
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class TaiageApplication {
|
public class TaiageApplication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arranca el contexto Spring Boot.
|
||||||
|
*
|
||||||
|
* @param args argumentos de línea de comandos (no se utilizan)
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(TaiageApplication.class, args);
|
SpringApplication.run(TaiageApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,13 @@ import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crea el usuario admin la primera vez que arranca la aplicación.
|
* Inicializa datos mínimos en base de datos al arrancar la aplicación.
|
||||||
* IMPORTANTE: cambia la contraseña en cuanto puedas desde el panel /admin/usuarios.
|
*
|
||||||
|
* <p>Si no existe ningún usuario con el email {@code admin@taiage.es}, crea
|
||||||
|
* automáticamente la cuenta de administrador con credenciales predeterminadas.</p>
|
||||||
|
*
|
||||||
|
* <p><strong>IMPORTANTE:</strong> cambia la contraseña por defecto en cuanto
|
||||||
|
* sea posible desde el panel {@code /admin/usuarios}.</p>
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class DataInitializer implements CommandLineRunner {
|
public class DataInitializer implements CommandLineRunner {
|
||||||
|
|
@ -19,11 +24,22 @@ public class DataInitializer implements CommandLineRunner {
|
||||||
private final UsuarioRepository repo;
|
private final UsuarioRepository repo;
|
||||||
private final PasswordEncoder encoder;
|
private final PasswordEncoder encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param repo repositorio de usuarios
|
||||||
|
* @param encoder encoder de contraseñas BCrypt
|
||||||
|
*/
|
||||||
public DataInitializer(UsuarioRepository repo, PasswordEncoder encoder) {
|
public DataInitializer(UsuarioRepository repo, PasswordEncoder encoder) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
this.encoder = encoder;
|
this.encoder = encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea el usuario administrador si todavía no existe en la base de datos.
|
||||||
|
*
|
||||||
|
* @param args argumentos de línea de comandos (no se utilizan)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run(String... args) {
|
public void run(String... args) {
|
||||||
if (repo.findByEmail("admin@taiage.es").isEmpty()) {
|
if (repo.findByEmail("admin@taiage.es").isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,41 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuración de seguridad de la aplicación.
|
||||||
|
*
|
||||||
|
* <p>Define las reglas de acceso HTTP, el proveedor de autenticación basado en
|
||||||
|
* base de datos y la configuración del formulario de login/logout.</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Rutas públicas: {@code /}, {@code /login}, {@code /registro}, {@code /leyes},
|
||||||
|
* {@code /noticias}, {@code /webhook/stripe} y recursos estáticos.</li>
|
||||||
|
* <li>Panel admin ({@code /admin/**}): requiere {@code ROLE_ADMIN}.</li>
|
||||||
|
* <li>Contenido de pago ({@code /curso/**}, {@code /api/**}): requiere
|
||||||
|
* {@code ROLE_PAGADO} o {@code ROLE_ADMIN}.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea el {@link PasswordEncoder} BCrypt usado en toda la aplicación.
|
||||||
|
*
|
||||||
|
* @return encoder BCrypt con factor de coste por defecto (10)
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea el proveedor de autenticación DAO que consulta {@link UsuarioService}.
|
||||||
|
*
|
||||||
|
* @param service servicio que implementa {@link org.springframework.security.core.userdetails.UserDetailsService}
|
||||||
|
* @param encoder encoder de contraseñas
|
||||||
|
* @return proveedor configurado
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public DaoAuthenticationProvider authProvider(UsuarioService service, PasswordEncoder encoder) {
|
public DaoAuthenticationProvider authProvider(UsuarioService service, PasswordEncoder encoder) {
|
||||||
var provider = new DaoAuthenticationProvider(service);
|
var provider = new DaoAuthenticationProvider(service);
|
||||||
|
|
@ -26,6 +52,13 @@ public class SecurityConfig {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define la cadena de filtros de seguridad HTTP.
|
||||||
|
*
|
||||||
|
* @param http objeto de configuración de Spring Security
|
||||||
|
* @return cadena de filtros construida
|
||||||
|
* @throws Exception si la configuración es inválida
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
|
|
@ -39,7 +72,7 @@ public class SecurityConfig {
|
||||||
"/", "/inicio", "/login", "/registro",
|
"/", "/inicio", "/login", "/registro",
|
||||||
"/leyes", "/noticias", "/acceso-denegado", "/error",
|
"/leyes", "/noticias", "/acceso-denegado", "/error",
|
||||||
"/webhook/stripe",
|
"/webhook/stripe",
|
||||||
"/css/**", "/js/**", "/images/**", "/leyes/**", "/favicon.ico"
|
"/css/**", "/js/**", "/images/**", "/leyes/**", "/audios/**", "/favicon.ico"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
// Panel de administración
|
// Panel de administración
|
||||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,23 @@ import jakarta.annotation.PostConstruct;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicializa el SDK de Stripe con la clave secreta de la aplicación.
|
||||||
|
*
|
||||||
|
* <p>La clave se obtiene de la propiedad {@code stripe.secret-key}, que debe
|
||||||
|
* definirse como variable de entorno {@code STRIPE_SECRET_KEY} en producción.</p>
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class StripeConfig {
|
public class StripeConfig {
|
||||||
|
|
||||||
|
/** Clave secreta de la cuenta Stripe (inyectada desde propiedades). */
|
||||||
@Value("${stripe.secret-key}")
|
@Value("${stripe.secret-key}")
|
||||||
private String secretKey;
|
private String secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asigna la clave secreta al SDK de Stripe tras la construcción del bean.
|
||||||
|
* Se ejecuta una sola vez al arrancar el contexto de Spring.
|
||||||
|
*/
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
Stripe.apiKey = secretKey;
|
Stripe.apiKey = secretKey;
|
||||||
|
|
|
||||||
|
|
@ -6,37 +6,74 @@ import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controlador MVC para el panel de administración de usuarios.
|
||||||
|
*
|
||||||
|
* <p>Todas las rutas bajo {@code /admin} requieren {@code ROLE_ADMIN}
|
||||||
|
* (configurado en {@link es.tatvil.taiageweb.config.SecurityConfig}).
|
||||||
|
* Permite listar, habilitar/deshabilitar, asignar acceso de pago,
|
||||||
|
* crear y eliminar usuarios.</p>
|
||||||
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/admin")
|
@RequestMapping("/admin")
|
||||||
public class AdminController {
|
public class AdminController {
|
||||||
|
|
||||||
private final UsuarioService service;
|
private final UsuarioService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param service servicio de gestión de usuarios
|
||||||
|
*/
|
||||||
public AdminController(UsuarioService service) {
|
public AdminController(UsuarioService service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista todos los usuarios registrados.
|
||||||
|
*
|
||||||
|
* @param model modelo Thymeleaf al que se añade el atributo {@code usuarios}
|
||||||
|
* @return nombre de la plantilla {@code admin/usuarios}
|
||||||
|
*/
|
||||||
@GetMapping({"", "/", "/usuarios"})
|
@GetMapping({"", "/", "/usuarios"})
|
||||||
public String listar(Model model) {
|
public String listar(Model model) {
|
||||||
model.addAttribute("usuarios", service.listarTodos());
|
model.addAttribute("usuarios", service.listarTodos());
|
||||||
return "admin/usuarios";
|
return "admin/usuarios";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Activa o desactiva la cuenta */
|
/**
|
||||||
|
* Activa o desactiva la cuenta del usuario indicado.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario
|
||||||
|
* @return redirección a {@code /admin/usuarios}
|
||||||
|
*/
|
||||||
@PostMapping("/usuarios/{id}/toggle-habilitado")
|
@PostMapping("/usuarios/{id}/toggle-habilitado")
|
||||||
public String toggleHabilitado(@PathVariable Long id) {
|
public String toggleHabilitado(@PathVariable Long id) {
|
||||||
service.toggleHabilitado(id);
|
service.toggleHabilitado(id);
|
||||||
return "redirect:/admin/usuarios";
|
return "redirect:/admin/usuarios";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Concede o revoca el acceso al curso */
|
/**
|
||||||
|
* Concede o revoca el acceso al curso ({@code ROLE_PAGADO}) del usuario indicado.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario
|
||||||
|
* @return redirección a {@code /admin/usuarios}
|
||||||
|
*/
|
||||||
@PostMapping("/usuarios/{id}/toggle-pagado")
|
@PostMapping("/usuarios/{id}/toggle-pagado")
|
||||||
public String togglePagado(@PathVariable Long id) {
|
public String togglePagado(@PathVariable Long id) {
|
||||||
service.toggleRolPagado(id);
|
service.toggleRolPagado(id);
|
||||||
return "redirect:/admin/usuarios";
|
return "redirect:/admin/usuarios";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** El admin crea un usuario directamente activo */
|
/**
|
||||||
|
* Crea un usuario directamente activo desde el panel de administración.
|
||||||
|
*
|
||||||
|
* @param email email del nuevo usuario
|
||||||
|
* @param password contraseña en texto plano
|
||||||
|
* @param pagado si {@code true}, se asigna {@code ROLE_PAGADO} además de {@code ROLE_USER}
|
||||||
|
* @param ra atributos flash para mensajes de error entre redirecciones
|
||||||
|
* @return redirección a {@code /admin/usuarios}
|
||||||
|
*/
|
||||||
@PostMapping("/usuarios/nuevo")
|
@PostMapping("/usuarios/nuevo")
|
||||||
public String crear(
|
public String crear(
|
||||||
@RequestParam String email,
|
@RequestParam String email,
|
||||||
|
|
@ -51,7 +88,12 @@ public class AdminController {
|
||||||
return "redirect:/admin/usuarios";
|
return "redirect:/admin/usuarios";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Elimina un usuario */
|
/**
|
||||||
|
* Elimina un usuario de la base de datos.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario a eliminar
|
||||||
|
* @return redirección a {@code /admin/usuarios}
|
||||||
|
*/
|
||||||
@PostMapping("/usuarios/{id}/eliminar")
|
@PostMapping("/usuarios/{id}/eliminar")
|
||||||
public String eliminar(@PathVariable Long id) {
|
public String eliminar(@PathVariable Long id) {
|
||||||
service.eliminar(id);
|
service.eliminar(id);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,20 @@ import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controlador MVC para el flujo de pago con Stripe.
|
||||||
|
*
|
||||||
|
* <p>Gestiona tres pasos del proceso de compra:</p>
|
||||||
|
* <ol>
|
||||||
|
* <li>Mostrar la página informativa con precio ({@code GET /pagar}).</li>
|
||||||
|
* <li>Crear la sesión de checkout en Stripe y redirigir al usuario
|
||||||
|
* ({@code POST /pagar/iniciar}).</li>
|
||||||
|
* <li>Mostrar páginas de éxito o cancelación tras volver de Stripe.</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>La concesión real del acceso ocurre vía webhook en
|
||||||
|
* {@link WebhookController}, no en las páginas de retorno de Stripe.</p>
|
||||||
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
public class PagoController {
|
public class PagoController {
|
||||||
|
|
||||||
|
|
@ -24,11 +38,25 @@ public class PagoController {
|
||||||
|
|
||||||
private final UsuarioRepository usuarioRepo;
|
private final UsuarioRepository usuarioRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param usuarioRepo repositorio de usuarios
|
||||||
|
*/
|
||||||
public PagoController(UsuarioRepository usuarioRepo) {
|
public PagoController(UsuarioRepository usuarioRepo) {
|
||||||
this.usuarioRepo = usuarioRepo;
|
this.usuarioRepo = usuarioRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Página informativa con precio y botón "Pagar ahora" */
|
/**
|
||||||
|
* Muestra la página informativa de pago con el precio actual.
|
||||||
|
*
|
||||||
|
* <p>Si el usuario ya tiene {@code ROLE_PAGADO}, se le redirige directamente
|
||||||
|
* al curso sin pasar por la pasarela.</p>
|
||||||
|
*
|
||||||
|
* @param auth autenticación del usuario en sesión (puede ser {@code null} si no está autenticado)
|
||||||
|
* @param model modelo Thymeleaf para pasar el precio formateado
|
||||||
|
* @return vista {@code pagar} o redirección a {@code /curso}
|
||||||
|
*/
|
||||||
@GetMapping("/pagar")
|
@GetMapping("/pagar")
|
||||||
public String mostrarPagina(Authentication auth, Model model) {
|
public String mostrarPagina(Authentication auth, Model model) {
|
||||||
// Si ya tiene acceso, redirigir directo al curso
|
// Si ya tiene acceso, redirigir directo al curso
|
||||||
|
|
@ -40,7 +68,17 @@ public class PagoController {
|
||||||
return "pagar";
|
return "pagar";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Crea la sesión de pago en Stripe y redirige a su pasarela */
|
/**
|
||||||
|
* Crea una sesión de pago en Stripe y redirige al usuario a la pasarela.
|
||||||
|
*
|
||||||
|
* <p>Utiliza el id del usuario como {@code clientReferenceId} para poder
|
||||||
|
* identificarlo cuando llegue el webhook de confirmación.</p>
|
||||||
|
*
|
||||||
|
* @param auth autenticación del usuario en sesión
|
||||||
|
* @param request petición HTTP para construir las URLs de retorno
|
||||||
|
* @return redirección a la URL de checkout de Stripe
|
||||||
|
* @throws StripeException si falla la comunicación con la API de Stripe
|
||||||
|
*/
|
||||||
@PostMapping("/pagar/iniciar")
|
@PostMapping("/pagar/iniciar")
|
||||||
public String iniciarPago(Authentication auth, HttpServletRequest request) throws StripeException {
|
public String iniciarPago(Authentication auth, HttpServletRequest request) throws StripeException {
|
||||||
String baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
|
String baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
|
||||||
|
|
@ -71,11 +109,24 @@ public class PagoController {
|
||||||
return "redirect:" + session.getUrl();
|
return "redirect:" + session.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Página de confirmación mostrada tras un pago exitoso.
|
||||||
|
*
|
||||||
|
* <p>El acceso real al curso se concede mediante el webhook de Stripe,
|
||||||
|
* no en esta página.</p>
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code pago-exito}
|
||||||
|
*/
|
||||||
@GetMapping("/pagar/exito")
|
@GetMapping("/pagar/exito")
|
||||||
public String exitoPago() {
|
public String exitoPago() {
|
||||||
return "pago-exito";
|
return "pago-exito";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Página mostrada cuando el usuario cancela el pago en Stripe.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code pago-cancelado}
|
||||||
|
*/
|
||||||
@GetMapping("/pagar/cancelado")
|
@GetMapping("/pagar/cancelado")
|
||||||
public String canceladoPago() {
|
public String canceladoPago() {
|
||||||
return "pago-cancelado";
|
return "pago-cancelado";
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,49 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controlador MVC para el auto-registro de nuevos usuarios.
|
||||||
|
*
|
||||||
|
* <p>Procesa el formulario de registro en {@code /registro}. La cuenta creada
|
||||||
|
* queda deshabilitada hasta que el administrador la active desde
|
||||||
|
* {@code /admin/usuarios}.</p>
|
||||||
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
public class RegistroController {
|
public class RegistroController {
|
||||||
|
|
||||||
private final UsuarioService service;
|
private final UsuarioService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param service servicio de gestión de usuarios
|
||||||
|
*/
|
||||||
public RegistroController(UsuarioService service) {
|
public RegistroController(UsuarioService service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Muestra el formulario de registro.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code registro}
|
||||||
|
*/
|
||||||
@GetMapping("/registro")
|
@GetMapping("/registro")
|
||||||
public String mostrarFormulario() {
|
public String mostrarFormulario() {
|
||||||
return "registro";
|
return "registro";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesa el formulario de registro.
|
||||||
|
*
|
||||||
|
* <p>Valida que las contraseñas coincidan y delega en {@link UsuarioService#registrar}.
|
||||||
|
* Si el registro tiene éxito, redirige al login con el parámetro {@code ?registrado}.</p>
|
||||||
|
*
|
||||||
|
* @param email email del nuevo usuario
|
||||||
|
* @param password contraseña elegida
|
||||||
|
* @param passwordConfirm confirmación de la contraseña
|
||||||
|
* @param model modelo Thymeleaf para enviar mensajes de error
|
||||||
|
* @return redirección a {@code /login?registrado} si éxito, o la vista {@code registro} con error
|
||||||
|
*/
|
||||||
@PostMapping("/registro")
|
@PostMapping("/registro")
|
||||||
public String registrar(
|
public String registrar(
|
||||||
@RequestParam String email,
|
@RequestParam String email,
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,32 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API REST que sirve los ficheros Markdown del temario.
|
||||||
|
*
|
||||||
|
* <p>Todos los recursos están bajo {@code /api/temas/**} y requieren
|
||||||
|
* autenticación con {@code ROLE_PAGADO} o {@code ROLE_ADMIN} (configurado en
|
||||||
|
* {@link es.tatvil.taiageweb.config.SecurityConfig}).</p>
|
||||||
|
*
|
||||||
|
* <p>Los ficheros se leen desde el classpath en {@code temas/} y se devuelven
|
||||||
|
* como texto plano UTF-8. Se protege contra ataques de <em>path traversal</em>
|
||||||
|
* rechazando rutas que contengan {@code ..} o {@code //}.</p>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/temas")
|
@RequestMapping("/api/temas")
|
||||||
public class TemaController {
|
public class TemaController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el contenido Markdown de un tema dado su ruta relativa.
|
||||||
|
*
|
||||||
|
* <p>Ejemplo de petición: {@code GET /api/temas/bloque1/tema1.md}</p>
|
||||||
|
*
|
||||||
|
* @param request petición HTTP de la que se extrae la ruta del recurso
|
||||||
|
* @return {@code 200 OK} con el contenido Markdown como texto plano,
|
||||||
|
* {@code 400 Bad Request} si la ruta es inválida,
|
||||||
|
* {@code 404 Not Found} si el fichero no existe
|
||||||
|
* @throws IOException si ocurre un error leyendo el recurso del classpath
|
||||||
|
*/
|
||||||
@GetMapping("/**")
|
@GetMapping("/**")
|
||||||
public ResponseEntity<String> getTema(HttpServletRequest request) throws IOException {
|
public ResponseEntity<String> getTema(HttpServletRequest request) throws IOException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,60 @@ import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sirve las páginas principales del sitio a través de Thymeleaf.
|
* Controlador MVC que sirve las páginas principales del sitio a través de Thymeleaf.
|
||||||
|
*
|
||||||
|
* <p>Todas las rutas devuelven el nombre de la plantilla correspondiente en
|
||||||
|
* {@code src/main/resources/templates/}. Spring Security controla qué rutas
|
||||||
|
* son accesibles sin autenticación.</p>
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
public class WebController {
|
public class WebController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Página de inicio.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code index}
|
||||||
|
*/
|
||||||
@GetMapping({"/", "/inicio"})
|
@GetMapping({"/", "/inicio"})
|
||||||
public String inicio() { return "index"; }
|
public String inicio() { return "index"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Página de inicio de sesión.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code login}
|
||||||
|
*/
|
||||||
@GetMapping("/login")
|
@GetMapping("/login")
|
||||||
public String login() { return "login"; }
|
public String login() { return "login"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visor del temario del curso (requiere {@code ROLE_PAGADO} o {@code ROLE_ADMIN}).
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code curso}
|
||||||
|
*/
|
||||||
@GetMapping("/curso")
|
@GetMapping("/curso")
|
||||||
public String curso() { return "curso"; }
|
public String curso() { return "curso"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navegador de legislación; acceso público.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code leyes}
|
||||||
|
*/
|
||||||
@GetMapping("/leyes")
|
@GetMapping("/leyes")
|
||||||
public String leyes() { return "leyes"; }
|
public String leyes() { return "leyes"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agregador de noticias INAP/BOE; acceso público.
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code noticias}
|
||||||
|
*/
|
||||||
@GetMapping("/noticias")
|
@GetMapping("/noticias")
|
||||||
public String noticias() { return "noticias"; }
|
public String noticias() { return "noticias"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Página de acceso denegado (HTTP 403).
|
||||||
|
*
|
||||||
|
* @return nombre de la plantilla {@code acceso-denegado}
|
||||||
|
*/
|
||||||
@GetMapping("/acceso-denegado")
|
@GetMapping("/acceso-denegado")
|
||||||
public String accesoDenegado() { return "acceso-denegado"; }
|
public String accesoDenegado() { return "acceso-denegado"; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,49 @@ import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controlador REST que recibe y procesa webhooks de Stripe.
|
||||||
|
*
|
||||||
|
* <p>Escucha en {@code POST /webhook/stripe} (ruta excluida de CSRF en
|
||||||
|
* {@link es.tatvil.taiageweb.config.SecurityConfig}). Verifica la firma
|
||||||
|
* del payload con el secreto de webhook antes de procesar ningún evento.</p>
|
||||||
|
*
|
||||||
|
* <p>Evento gestionado:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code checkout.session.completed} – concede acceso al curso al usuario
|
||||||
|
* identificado por {@code clientReferenceId}.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
public class WebhookController {
|
public class WebhookController {
|
||||||
|
|
||||||
|
/** Secreto de webhook de Stripe (inyectado desde {@code STRIPE_WEBHOOK_SECRET}). */
|
||||||
@Value("${stripe.webhook-secret}")
|
@Value("${stripe.webhook-secret}")
|
||||||
private String webhookSecret;
|
private String webhookSecret;
|
||||||
|
|
||||||
private final UsuarioService usuarioService;
|
private final UsuarioService usuarioService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param usuarioService servicio de gestión de usuarios
|
||||||
|
*/
|
||||||
public WebhookController(UsuarioService usuarioService) {
|
public WebhookController(UsuarioService usuarioService) {
|
||||||
this.usuarioService = usuarioService;
|
this.usuarioService = usuarioService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint receptor de eventos de Stripe.
|
||||||
|
*
|
||||||
|
* <p>Verifica la firma {@code Stripe-Signature} del payload. Si es válida
|
||||||
|
* y el evento es {@code checkout.session.completed}, concede
|
||||||
|
* {@code ROLE_PAGADO} al usuario referenciado.</p>
|
||||||
|
*
|
||||||
|
* @param payload cuerpo crudo de la petición HTTP (necesario para verificar la firma)
|
||||||
|
* @param sigHeader valor del header {@code Stripe-Signature}
|
||||||
|
* @return {@code 200 OK} si el evento se procesa correctamente,
|
||||||
|
* {@code 400 Bad Request} si la firma es inválida
|
||||||
|
*/
|
||||||
@PostMapping("/webhook/stripe")
|
@PostMapping("/webhook/stripe")
|
||||||
public ResponseEntity<String> stripeWebhook(
|
public ResponseEntity<String> stripeWebhook(
|
||||||
@RequestBody String payload,
|
@RequestBody String payload,
|
||||||
|
|
|
||||||
|
|
@ -4,55 +4,135 @@ import jakarta.persistence.*;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entidad JPA que representa a un usuario registrado en la plataforma.
|
||||||
|
*
|
||||||
|
* <p>Los usuarios se almacenan en la tabla {@code usuarios}. Sus roles se guardan
|
||||||
|
* en la tabla auxiliar {@code usuario_roles} mediante una relación
|
||||||
|
* {@code @ElementCollection}.</p>
|
||||||
|
*
|
||||||
|
* <h3>Roles disponibles</h3>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code ROLE_USER} – usuario registrado, sin acceso al curso.</li>
|
||||||
|
* <li>{@code ROLE_PAGADO} – ha completado el pago; accede al temario completo.</li>
|
||||||
|
* <li>{@code ROLE_ADMIN} – administrador; acceso total al panel de gestión.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "usuarios")
|
@Table(name = "usuarios")
|
||||||
public class Usuario {
|
public class Usuario {
|
||||||
|
|
||||||
|
/** Identificador autogenerado (clave primaria). */
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/** Dirección de correo electrónico; única e indexada, se usa como nombre de usuario. */
|
||||||
@Column(unique = true, nullable = false, length = 100)
|
@Column(unique = true, nullable = false, length = 100)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
/** Contraseña cifrada con BCrypt. Nunca se almacena en texto plano. */
|
||||||
@Column(nullable = false, length = 255)
|
@Column(nullable = false, length = 255)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
/** Cuenta activa (admin la activa; pago automático también puede activarla) */
|
/**
|
||||||
|
* Indica si la cuenta está activa.
|
||||||
|
* <p>Las cuentas nuevas arrancan con {@code false}; el administrador o un pago
|
||||||
|
* completado la activan automáticamente.</p>
|
||||||
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean habilitado = false;
|
private boolean habilitado = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Roles posibles:
|
* Conjunto de roles asignados al usuario.
|
||||||
* ROLE_USER – registrado (sin acceso al curso)
|
* Los valores posibles son {@code ROLE_USER}, {@code ROLE_PAGADO} y {@code ROLE_ADMIN}.
|
||||||
* ROLE_PAGADO – ha pagado (acceso al curso)
|
|
||||||
* ROLE_ADMIN – administrador (acceso total)
|
|
||||||
*/
|
*/
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@CollectionTable(name = "usuario_roles", joinColumns = @JoinColumn(name = "usuario_id"))
|
@CollectionTable(name = "usuario_roles", joinColumns = @JoinColumn(name = "usuario_id"))
|
||||||
@Column(name = "rol")
|
@Column(name = "rol")
|
||||||
private Set<String> roles = new HashSet<>();
|
private Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
|
/** Constructor sin argumentos requerido por JPA. */
|
||||||
public Usuario() {}
|
public Usuario() {}
|
||||||
|
|
||||||
// ── Getters / Setters ────────────────────────────────────
|
// ── Getters / Setters ────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el identificador único del usuario.
|
||||||
|
*
|
||||||
|
* @return id generado por la base de datos
|
||||||
|
*/
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el email del usuario.
|
||||||
|
*
|
||||||
|
* @return dirección de correo tal como fue registrada
|
||||||
|
*/
|
||||||
public String getEmail() { return email; }
|
public String getEmail() { return email; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establece el email del usuario.
|
||||||
|
*
|
||||||
|
* @param email dirección de correo electrónico
|
||||||
|
*/
|
||||||
public void setEmail(String email) { this.email = email; }
|
public void setEmail(String email) { this.email = email; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la contraseña cifrada.
|
||||||
|
*
|
||||||
|
* @return hash BCrypt de la contraseña
|
||||||
|
*/
|
||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establece la contraseña (debe ser el hash BCrypt, nunca texto plano).
|
||||||
|
*
|
||||||
|
* @param password hash BCrypt
|
||||||
|
*/
|
||||||
public void setPassword(String password) { this.password = password; }
|
public void setPassword(String password) { this.password = password; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indica si la cuenta está habilitada para iniciar sesión.
|
||||||
|
*
|
||||||
|
* @return {@code true} si la cuenta está activa
|
||||||
|
*/
|
||||||
public boolean isHabilitado() { return habilitado; }
|
public boolean isHabilitado() { return habilitado; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activa o desactiva la cuenta.
|
||||||
|
*
|
||||||
|
* @param habilitado {@code true} para habilitar la cuenta
|
||||||
|
*/
|
||||||
public void setHabilitado(boolean habilitado) { this.habilitado = habilitado; }
|
public void setHabilitado(boolean habilitado) { this.habilitado = habilitado; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el conjunto de roles asignados al usuario.
|
||||||
|
*
|
||||||
|
* @return conjunto mutable de roles
|
||||||
|
*/
|
||||||
public Set<String> getRoles() { return roles; }
|
public Set<String> getRoles() { return roles; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reemplaza el conjunto de roles del usuario.
|
||||||
|
*
|
||||||
|
* @param roles nuevo conjunto de roles
|
||||||
|
*/
|
||||||
public void setRoles(Set<String> roles) { this.roles = roles; }
|
public void setRoles(Set<String> roles) { this.roles = roles; }
|
||||||
|
|
||||||
// ── Helpers para la vista ────────────────────────────────
|
// ── Helpers para la vista ────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indica si el usuario tiene acceso al curso (rol {@code ROLE_PAGADO}).
|
||||||
|
*
|
||||||
|
* @return {@code true} si el rol está presente
|
||||||
|
*/
|
||||||
public boolean isPagado() { return roles.contains("ROLE_PAGADO"); }
|
public boolean isPagado() { return roles.contains("ROLE_PAGADO"); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indica si el usuario es administrador (rol {@code ROLE_ADMIN}).
|
||||||
|
*
|
||||||
|
* @return {@code true} si el rol está presente
|
||||||
|
*/
|
||||||
public boolean isAdmin() { return roles.contains("ROLE_ADMIN"); }
|
public boolean isAdmin() { return roles.contains("ROLE_ADMIN"); }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,27 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repositorio JPA para la entidad {@link Usuario}.
|
||||||
|
*
|
||||||
|
* <p>Extiende {@link JpaRepository} con métodos de búsqueda adicionales
|
||||||
|
* necesarios para la autenticación y el registro de usuarios.</p>
|
||||||
|
*/
|
||||||
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
|
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca un usuario por su dirección de correo electrónico.
|
||||||
|
*
|
||||||
|
* @param email email del usuario a buscar
|
||||||
|
* @return {@link Optional} con el usuario si existe, vacío en caso contrario
|
||||||
|
*/
|
||||||
Optional<Usuario> findByEmail(String email);
|
Optional<Usuario> findByEmail(String email);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprueba si ya existe un usuario registrado con el email dado.
|
||||||
|
*
|
||||||
|
* @param email email a verificar
|
||||||
|
* @return {@code true} si el email ya está en uso
|
||||||
|
*/
|
||||||
boolean existsByEmail(String email);
|
boolean existsByEmail(String email);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,39 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servicio de negocio para la gestión de usuarios.
|
||||||
|
*
|
||||||
|
* <p>Implementa {@link UserDetailsService} para la integración con Spring Security
|
||||||
|
* y centraliza todas las operaciones sobre la entidad {@link Usuario}: registro,
|
||||||
|
* activación, asignación de roles y eliminación.</p>
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class UsuarioService implements UserDetailsService {
|
public class UsuarioService implements UserDetailsService {
|
||||||
|
|
||||||
private final UsuarioRepository repo;
|
private final UsuarioRepository repo;
|
||||||
private final PasswordEncoder encoder;
|
private final PasswordEncoder encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias.
|
||||||
|
*
|
||||||
|
* @param repo repositorio de usuarios
|
||||||
|
* @param encoder encoder de contraseñas BCrypt
|
||||||
|
*/
|
||||||
public UsuarioService(UsuarioRepository repo, PasswordEncoder encoder) {
|
public UsuarioService(UsuarioRepository repo, PasswordEncoder encoder) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
this.encoder = encoder;
|
this.encoder = encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Spring Security ──────────────────────────────────────
|
// ── Spring Security ──────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carga los detalles de un usuario por su email para Spring Security.
|
||||||
|
*
|
||||||
|
* @param email email del usuario (campo {@code username} del formulario de login)
|
||||||
|
* @return detalles del usuario listos para la autenticación
|
||||||
|
* @throws UsernameNotFoundException si no existe ningún usuario con ese email
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||||
Usuario u = repo.findByEmail(email)
|
Usuario u = repo.findByEmail(email)
|
||||||
|
|
@ -48,8 +68,13 @@ public class UsuarioService implements UserDetailsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Auto-registro (pendiente de activación) ──────────────
|
// ── Auto-registro (pendiente de activación) ──────────────
|
||||||
|
/**
|
||||||
@Transactional
|
* Registra un nuevo usuario con la cuenta deshabilitada hasta que el admin la active.
|
||||||
|
*
|
||||||
|
* @param email email del nuevo usuario
|
||||||
|
* @param passwordPlana contraseña en texto plano (mínimo 8 caracteres)
|
||||||
|
* @throws IllegalArgumentException si el email ya está registrado o la contraseña es demasiado corta
|
||||||
|
*/ @Transactional
|
||||||
public void registrar(String email, String passwordPlana) {
|
public void registrar(String email, String passwordPlana) {
|
||||||
if (repo.existsByEmail(email)) {
|
if (repo.existsByEmail(email)) {
|
||||||
throw new IllegalArgumentException("El email ya está registrado");
|
throw new IllegalArgumentException("El email ya está registrado");
|
||||||
|
|
@ -66,19 +91,32 @@ public class UsuarioService implements UserDetailsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Consultas ────────────────────────────────────────────
|
// ── Consultas ────────────────────────────────────────────
|
||||||
|
/**
|
||||||
public List<Usuario> listarTodos() {
|
* Devuelve todos los usuarios registrados.
|
||||||
|
*
|
||||||
|
* @return lista de usuarios ordenada por ID
|
||||||
|
*/ public List<Usuario> listarTodos() {
|
||||||
return repo.findAll();
|
return repo.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Acciones de admin ────────────────────────────────────
|
// ── Acciones de admin ────────────────────────────────────
|
||||||
|
/**
|
||||||
@Transactional
|
* Invierte el estado de habilitación de la cuenta de un usuario.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario
|
||||||
|
* @throws java.util.NoSuchElementException si no existe un usuario con ese id
|
||||||
|
*/ @Transactional
|
||||||
public void toggleHabilitado(Long id) {
|
public void toggleHabilitado(Long id) {
|
||||||
Usuario u = repo.findById(id).orElseThrow();
|
Usuario u = repo.findById(id).orElseThrow();
|
||||||
u.setHabilitado(!u.isHabilitado());
|
u.setHabilitado(!u.isHabilitado());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concede o revoca el rol {@code ROLE_PAGADO} de un usuario.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario
|
||||||
|
* @throws java.util.NoSuchElementException si no existe un usuario con ese id
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void toggleRolPagado(Long id) {
|
public void toggleRolPagado(Long id) {
|
||||||
Usuario u = repo.findById(id).orElseThrow();
|
Usuario u = repo.findById(id).orElseThrow();
|
||||||
|
|
@ -89,7 +127,15 @@ public class UsuarioService implements UserDetailsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Concede acceso al curso tras un pago confirmado por Stripe (nunca quita el rol) */
|
/**
|
||||||
|
* Concede acceso al curso tras un pago confirmado por Stripe.
|
||||||
|
*
|
||||||
|
* <p>Añade {@code ROLE_PAGADO} y activa la cuenta si estuviera deshabilitada.
|
||||||
|
* Este método <em>nunca</em> quita el rol una vez concedido.</p>
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario
|
||||||
|
* @throws java.util.NoSuchElementException si no existe un usuario con ese id
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void darAccesoPagado(Long id) {
|
public void darAccesoPagado(Long id) {
|
||||||
Usuario u = repo.findById(id).orElseThrow();
|
Usuario u = repo.findById(id).orElseThrow();
|
||||||
|
|
@ -97,7 +143,14 @@ public class UsuarioService implements UserDetailsService {
|
||||||
u.setHabilitado(true);
|
u.setHabilitado(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** El admin crea directamente un usuario ya activo */
|
/**
|
||||||
|
* Crea un usuario directamente activo desde el panel de administración.
|
||||||
|
*
|
||||||
|
* @param email email del nuevo usuario
|
||||||
|
* @param passwordPlana contraseña en texto plano
|
||||||
|
* @param pagado si {@code true}, se añade {@code ROLE_PAGADO} además de {@code ROLE_USER}
|
||||||
|
* @throws IllegalArgumentException si el email ya está registrado
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void crearPorAdmin(String email, String passwordPlana, boolean pagado) {
|
public void crearPorAdmin(String email, String passwordPlana, boolean pagado) {
|
||||||
if (repo.existsByEmail(email)) {
|
if (repo.existsByEmail(email)) {
|
||||||
|
|
@ -114,6 +167,11 @@ public class UsuarioService implements UserDetailsService {
|
||||||
repo.save(u);
|
repo.save(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elimina un usuario de la base de datos.
|
||||||
|
*
|
||||||
|
* @param id identificador del usuario a eliminar
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void eliminar(Long id) {
|
public void eliminar(Long id) {
|
||||||
repo.deleteById(id);
|
repo.deleteById(id);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue