mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 08:58:48 +00:00
trabajando en usuarios
This commit is contained in:
@ -1,36 +1,105 @@
|
||||
package com.imprimelibros.erp.config;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import com.imprimelibros.erp.users.UserDao;
|
||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/",
|
||||
"/assets/**",
|
||||
"/css/**",
|
||||
"/js/**",
|
||||
"/images/**",
|
||||
"/public/**",
|
||||
"/error",
|
||||
"/presupuesto/public/**",
|
||||
"/favicon.ico")
|
||||
.permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers("/presupuesto/public/**"))
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login")
|
||||
.permitAll())
|
||||
.logout(logout -> logout.permitAll());
|
||||
private final DataSource dataSource;
|
||||
|
||||
return http.build();
|
||||
}
|
||||
public SecurityConfig(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
// UserDetailsService para autenticación por username
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService(UserDao repo) {
|
||||
return username -> {
|
||||
User u = repo.findByUserNameAndEnabledTrue(username);
|
||||
if (u == null)
|
||||
throw new UsernameNotFoundException("No existe: " + username);
|
||||
return new UserDetailsImpl(u);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
// Repositorio de tokens persistentes (usa la tabla 'persistent_logins')
|
||||
@Bean
|
||||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
||||
repo.setDataSource(dataSource);
|
||||
// Descomenta una única vez si quieres que cree la tabla automáticamente:
|
||||
// repo.setCreateTableOnStartup(true);
|
||||
return repo;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, @Value("${security.rememberme.key}") String keyRememberMe) throws Exception {
|
||||
http
|
||||
.sessionManagement(session -> session
|
||||
.invalidSessionUrl("/login?expired")
|
||||
.maximumSessions(1) // opcional: limita sesiones concurrentes
|
||||
)
|
||||
// CSRF habilitado; ignoramos endpoints públicos del presupuesto (AJAX)
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers("/presupuesto/public/**"))
|
||||
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/", "/login",
|
||||
"/assets/**", "/css/**", "/js/**", "/images/**",
|
||||
"/public/**", "/presupuesto/public/**",
|
||||
"/error", "/favicon.ico")
|
||||
.permitAll()
|
||||
.anyRequest().authenticated())
|
||||
|
||||
.authorizeHttpRequests(configurer -> configurer
|
||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||
)
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login").permitAll()
|
||||
.loginProcessingUrl("/login")
|
||||
.usernameParameter("username")
|
||||
.passwordParameter("password")
|
||||
.defaultSuccessUrl("/", true))
|
||||
|
||||
// ===== Remember Me =====
|
||||
.rememberMe(rm -> rm
|
||||
.key(keyRememberMe) // clave secreta
|
||||
.rememberMeParameter("remember-me") // <input name="remember-me">
|
||||
.rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
|
||||
.tokenValiditySeconds(60 * 60 * 24 * 14) // 14 días
|
||||
.userDetailsService(userDetailsService(null)) // se inyecta el bean real
|
||||
// en runtime
|
||||
.tokenRepository(persistentTokenRepository()))
|
||||
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/logout")
|
||||
.logoutSuccessUrl("/")
|
||||
.invalidateHttpSession(true)
|
||||
.deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
|
||||
.permitAll());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.data.domain.*;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DataTable<T> {
|
||||
|
||||
public interface FilterHook<T> extends BiConsumer<SpecBuilder<T>, DataTablesRequest> {}
|
||||
public interface SpecBuilder<T> { void add(Specification<T> extra); }
|
||||
|
||||
private final JpaSpecificationExecutor<T> repo;
|
||||
private final Class<T> entityClass;
|
||||
private final DataTablesRequest dt;
|
||||
private final List<String> searchable;
|
||||
private final List<Function<T, Map<String,Object>>> adders = new ArrayList<>();
|
||||
private final List<Function<Map<String,Object>, Map<String,Object>>> editors = new ArrayList<>();
|
||||
private final List<FilterHook<T>> filters = new ArrayList<>();
|
||||
private Specification<T> baseSpec = (root,q,cb) -> cb.conjunction();
|
||||
private final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
private DataTable(JpaSpecificationExecutor<T> repo, Class<T> entityClass, DataTablesRequest dt, List<String> searchable) {
|
||||
this.repo = repo; this.entityClass = entityClass; this.dt = dt; this.searchable = searchable;
|
||||
}
|
||||
|
||||
public static <T> DataTable<T> of(JpaSpecificationExecutor<T> repo, Class<T> entityClass, DataTablesRequest dt, List<String> searchable) {
|
||||
return new DataTable<>(repo, entityClass, dt, searchable);
|
||||
}
|
||||
|
||||
/** Equivalente a tu $q->where(...): establece condición base */
|
||||
public DataTable<T> where(Specification<T> spec) { this.baseSpec = this.baseSpec.and(spec); return this; }
|
||||
|
||||
/** add("campo", fn(entity)->valor|Map) */
|
||||
public DataTable<T> add(String field, Function<T, Object> fn) {
|
||||
adders.add(entity -> {
|
||||
Map<String,Object> m = new HashMap<>();
|
||||
m.put(field, fn.apply(entity));
|
||||
return m;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** add(fn(entity)->Map<String,Object>) para devolver objetos anidados como tu "logo" */
|
||||
public DataTable<T> add(Function<T, Map<String,Object>> fn) { adders.add(fn); return this; }
|
||||
|
||||
/** edit("campo", fn(entity)->valor) sobreescribe un campo existente o lo crea si no existe */
|
||||
public DataTable<T> edit(String field, Function<T, Object> fn) {
|
||||
editors.add(row -> { row.put(field, fn.apply((T)row.get("__entity"))); return row; });
|
||||
return this;
|
||||
}
|
||||
|
||||
/** filter((builder, req) -> builder.add(miExtraSpec(req))) */
|
||||
public DataTable<T> filter(FilterHook<T> hook) { filters.add(hook); return this; }
|
||||
|
||||
public DataTablesResponse<Map<String,Object>> toJson(long totalCount) {
|
||||
// Construye spec con búsqueda global + base + filtros custom
|
||||
Specification<T> spec = baseSpec.and(DataTablesSpecification.build(dt, searchable));
|
||||
final Specification<T>[] holder = new Specification[]{ spec };
|
||||
filters.forEach(h -> h.accept(extra -> holder[0] = holder[0].and(extra), dt));
|
||||
spec = holder[0];
|
||||
|
||||
// Sort
|
||||
Sort sort = Sort.unsorted();
|
||||
if (!dt.order.isEmpty() && !dt.columns.isEmpty()) {
|
||||
List<Sort.Order> orders = new ArrayList<>();
|
||||
for (var o : dt.order) {
|
||||
String field = dt.columns.get(o.column).name;
|
||||
orders.add(new Sort.Order("desc".equalsIgnoreCase(o.dir) ? Sort.Direction.DESC : Sort.Direction.ASC, field));
|
||||
}
|
||||
sort = Sort.by(orders);
|
||||
}
|
||||
|
||||
// Page
|
||||
int page = dt.length > 0 ? dt.start / dt.length : 0;
|
||||
Pageable pageable = dt.length > 0 ? PageRequest.of(page, dt.length, sort) : Pageable.unpaged();
|
||||
|
||||
var p = repo.findAll(holder[0], pageable);
|
||||
long filtered = p.getTotalElements();
|
||||
|
||||
// Mapear entidad -> Map base (via Jackson) + add/edit
|
||||
List<Map<String,Object>> data = new ArrayList<>();
|
||||
for (T e : p.getContent()) {
|
||||
Map<String,Object> row = om.convertValue(e, Map.class);
|
||||
row.put("__entity", e); // para editores que necesiten la entidad
|
||||
for (var ad : adders) row.putAll(ad.apply(e));
|
||||
for (var ed : editors) ed.apply(row);
|
||||
row.remove("__entity");
|
||||
data.add(row);
|
||||
}
|
||||
return new DataTablesResponse<>(dt.draw, totalCount, filtered, data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
public class DataTablesParser {
|
||||
public static DataTablesRequest from(HttpServletRequest req) {
|
||||
DataTablesRequest dt = new DataTablesRequest();
|
||||
dt.draw = parseInt(req.getParameter("draw"), 0);
|
||||
dt.start = parseInt(req.getParameter("start"), 0);
|
||||
dt.length = parseInt(req.getParameter("length"), 10);
|
||||
|
||||
if (req.getParameter("search[value]") != null) {
|
||||
dt.search.value = req.getParameter("search[value]");
|
||||
dt.search.regex = Boolean.parseBoolean(req.getParameter("search[regex]"));
|
||||
}
|
||||
|
||||
for (int i=0;; i++) {
|
||||
String data = req.getParameter("columns["+i+"][data]");
|
||||
if (data == null) break;
|
||||
DataTablesRequest.Column c = new DataTablesRequest.Column();
|
||||
c.data = data;
|
||||
c.name = Optional.ofNullable(req.getParameter("columns["+i+"][name]")).orElse(data);
|
||||
c.searchable = Boolean.parseBoolean(Optional.ofNullable(req.getParameter("columns["+i+"][searchable]")).orElse("true"));
|
||||
c.orderable = Boolean.parseBoolean(Optional.ofNullable(req.getParameter("columns["+i+"][orderable]")).orElse("true"));
|
||||
c.search.value = Optional.ofNullable(req.getParameter("columns["+i+"][search][value]")).orElse("");
|
||||
dt.columns.add(c);
|
||||
}
|
||||
for (int i=0;; i++) {
|
||||
String colIdx = req.getParameter("order["+i+"][column]");
|
||||
if (colIdx == null) break;
|
||||
DataTablesRequest.Order o = new DataTablesRequest.Order();
|
||||
o.column = parseInt(colIdx,0);
|
||||
o.dir = Optional.ofNullable(req.getParameter("order["+i+"][dir]")).orElse("asc");
|
||||
dt.order.add(o);
|
||||
}
|
||||
|
||||
// guarda TODOS los params crudos (para filtros custom)
|
||||
req.getParameterMap().forEach((k,v) -> dt.raw.put(k, v!=null && v.length>0 ? v[0] : null));
|
||||
return dt;
|
||||
}
|
||||
private static int parseInt(String s, int def){ try{return Integer.parseInt(s);}catch(Exception e){return def;}}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DataTablesRequest {
|
||||
public int draw;
|
||||
public int start;
|
||||
public int length;
|
||||
public Search search = new Search();
|
||||
public List<Order> order = new ArrayList<>();
|
||||
public List<Column> columns = new ArrayList<>();
|
||||
public Map<String,String> raw = new HashMap<>(); // <- params extra
|
||||
|
||||
public static class Search { public String value=""; public boolean regex; }
|
||||
public static class Order { public int column; public String dir; }
|
||||
public static class Column {
|
||||
public String data;
|
||||
public String name;
|
||||
public boolean searchable=true;
|
||||
public boolean orderable=true;
|
||||
public Search search=new Search();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DataTablesResponse<T> {
|
||||
public int draw;
|
||||
public long recordsTotal;
|
||||
public long recordsFiltered;
|
||||
public List<T> data;
|
||||
|
||||
public DataTablesResponse(int draw, long total, long filtered, List<T> data) {
|
||||
this.draw = draw;
|
||||
this.recordsTotal = total;
|
||||
this.recordsFiltered = filtered;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import org.springframework.data.domain.*;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DataTablesService {
|
||||
|
||||
public static <T> DataTablesResponse<T> handle(
|
||||
DataTablesRequest dt,
|
||||
JpaSpecificationExecutor<T> repo,
|
||||
long totalCount, // count sin filtros (cacheable)
|
||||
List<String> searchableFields,
|
||||
Class<T> entityClass
|
||||
) {
|
||||
// Spec (filtros)
|
||||
Specification<T> spec = DataTablesSpecification.build(dt, searchableFields);
|
||||
|
||||
// Sort
|
||||
Sort sort = Sort.unsorted();
|
||||
if (!dt.order.isEmpty() && !dt.columns.isEmpty()) {
|
||||
List<Sort.Order> orders = new ArrayList<>();
|
||||
for (DataTablesRequest.Order o : dt.order) {
|
||||
String field = dt.columns.get(o.column).name;
|
||||
orders.add(new Sort.Order("desc".equalsIgnoreCase(o.dir) ? Sort.Direction.DESC : Sort.Direction.ASC, field));
|
||||
}
|
||||
sort = Sort.by(orders);
|
||||
}
|
||||
|
||||
// Page
|
||||
int page = dt.length > 0 ? dt.start / dt.length : 0;
|
||||
Pageable pageable = dt.length > 0 ? PageRequest.of(page, dt.length, sort) : Pageable.unpaged();
|
||||
|
||||
// Query
|
||||
Page<T> result = repo.findAll(spec, pageable);
|
||||
long filtered = result.getTotalElements();
|
||||
|
||||
return new DataTablesResponse<>(
|
||||
dt.draw,
|
||||
totalCount,
|
||||
filtered,
|
||||
result.getContent()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.imprimelibros.erp.datatables;
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import jakarta.persistence.criteria.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DataTablesSpecification {
|
||||
|
||||
/**
|
||||
* Crea una Specification con búsqueda global y por columna (LIKE case-insensitive)
|
||||
* @param dt request de datatables
|
||||
* @param searchableFields campos del entity para el buscador global
|
||||
*/
|
||||
public static <T> Specification<T> build(DataTablesRequest dt, List<String> searchableFields) {
|
||||
return (root, query, cb) -> {
|
||||
List<Predicate> ands = new ArrayList<>();
|
||||
|
||||
// Filtro por columna (si quieres soportarlo)
|
||||
for (int i = 0; i < dt.columns.size(); i++) {
|
||||
DataTablesRequest.Column col = dt.columns.get(i);
|
||||
if (col.searchable && col.search != null && col.search.value != null && !col.search.value.isEmpty()) {
|
||||
ands.add(like(cb, root.get(col.name), col.search.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Búsqueda global
|
||||
if (dt.search != null && dt.search.value != null && !dt.search.value.isEmpty() && !searchableFields.isEmpty()) {
|
||||
String term = "%" + dt.search.value.trim().toLowerCase() + "%";
|
||||
List<Predicate> ors = new ArrayList<>();
|
||||
for (String f : searchableFields) {
|
||||
ors.add(cb.like(cb.lower(root.get(f).as(String.class)), term));
|
||||
}
|
||||
ands.add(cb.or(ors.toArray(new Predicate[0])));
|
||||
}
|
||||
|
||||
return ands.isEmpty() ? cb.conjunction() : cb.and(ands.toArray(new Predicate[0]));
|
||||
};
|
||||
}
|
||||
|
||||
private static Predicate like(CriteriaBuilder cb, Path<?> path, String value) {
|
||||
return cb.like(cb.lower(path.as(String.class)), "%" + value.trim().toLowerCase() + "%");
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,11 @@ public class HomeController {
|
||||
model.addAttribute("ancho_alto_min", variableService.getValorEntero("ancho_alto_min"));
|
||||
model.addAttribute("ancho_alto_max", variableService.getValorEntero("ancho_alto_max"));
|
||||
}
|
||||
else{
|
||||
// empty translations for authenticated users
|
||||
Map<String, String> translations = Map.of();
|
||||
model.addAttribute("languageBundle", translations);
|
||||
}
|
||||
return "imprimelibros/home";
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@ public class LoginController {
|
||||
|
||||
@GetMapping("/login")
|
||||
public String index(Model model, Locale locale) {
|
||||
model.addAttribute("form", "_login");
|
||||
return "imprimelibros/login/login";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
49
src/main/java/com/imprimelibros/erp/users/Role.java
Normal file
49
src/main/java/com/imprimelibros/erp/users/Role.java
Normal file
@ -0,0 +1,49 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "roles")
|
||||
public class Role {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
public Role() {
|
||||
}
|
||||
|
||||
public Role(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Role{" + "id=" + id + ", name='" + name + '\'' + '}';
|
||||
}
|
||||
}
|
||||
9
src/main/java/com/imprimelibros/erp/users/RoleDao.java
Normal file
9
src/main/java/com/imprimelibros/erp/users/RoleDao.java
Normal file
@ -0,0 +1,9 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import com.imprimelibros.erp.users.Role;
|
||||
|
||||
public interface RoleDao {
|
||||
|
||||
public Role findRoleByName(String theRoleName);
|
||||
|
||||
}
|
||||
33
src/main/java/com/imprimelibros/erp/users/RoleDaoImpl.java
Normal file
33
src/main/java/com/imprimelibros/erp/users/RoleDaoImpl.java
Normal file
@ -0,0 +1,33 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class RoleDaoImpl implements RoleDao {
|
||||
|
||||
private EntityManager entityManager;
|
||||
|
||||
public RoleDaoImpl(EntityManager theEntityManager) {
|
||||
entityManager = theEntityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role findRoleByName(String theRoleName) {
|
||||
|
||||
// retrieve/read from database using name
|
||||
TypedQuery<Role> theQuery = entityManager.createQuery("from Role where name=:roleName", Role.class);
|
||||
theQuery.setParameter("roleName", theRoleName);
|
||||
|
||||
Role theRole = null;
|
||||
|
||||
try {
|
||||
theRole = theQuery.getSingleResult();
|
||||
} catch (Exception e) {
|
||||
theRole = null;
|
||||
}
|
||||
|
||||
return theRole;
|
||||
}
|
||||
}
|
||||
116
src/main/java/com/imprimelibros/erp/users/User.java
Normal file
116
src/main/java/com/imprimelibros/erp/users/User.java
Normal file
@ -0,0 +1,116 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.util.Collection;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "fullname")
|
||||
private String fullName;
|
||||
|
||||
@Column(name = "username")
|
||||
private String userName;
|
||||
|
||||
@Column(name = "password")
|
||||
private String password;
|
||||
|
||||
@Column(name = "enabled")
|
||||
private boolean enabled;
|
||||
|
||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
@JoinTable(name = "users_roles",
|
||||
joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "role_id"))
|
||||
private Collection<Role> roles;
|
||||
|
||||
|
||||
/* Constructors */
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String fullName, String userName, String password, boolean enabled) {
|
||||
this.fullName = fullName;
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public User(String fullName, String userName, String password, boolean enabled,
|
||||
Collection<Role> roles) {
|
||||
this.fullName = fullName;
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
this.enabled = enabled;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/* Getters and Setters */
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Collection<Role> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Collection<Role> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", fullName='" + fullName + '\'' +
|
||||
", userName='" + userName + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
", enabled=" + enabled +
|
||||
", roles=" + roles +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
||||
import com.imprimelibros.erp.datatables.DataTablesParser;
|
||||
import com.imprimelibros.erp.datatables.DataTable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/users")
|
||||
public class UserController {
|
||||
|
||||
private UserDao repo;
|
||||
|
||||
public UserController(UserDao repo, UserService userService) {
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN') or hasRole('SUPERADMIN')")
|
||||
@GetMapping("/")
|
||||
public DataTablesResponse<Map<String,Object>> datatable(HttpServletRequest request) {
|
||||
|
||||
DataTablesRequest dt = DataTablesParser.from(request);
|
||||
|
||||
Specification<User> base = (root, query, cb) -> cb.conjunction();
|
||||
long total = repo.count();
|
||||
|
||||
return DataTable
|
||||
.of(repo, User.class, dt, List.of(
|
||||
"username", "email", "role" // campos buscables
|
||||
))
|
||||
.where(base)
|
||||
.toJson(total);
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/imprimelibros/erp/users/UserDao.java
Normal file
12
src/main/java/com/imprimelibros/erp/users/UserDao.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@Repository
|
||||
public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
|
||||
|
||||
|
||||
User findByUserNameAndEnabledTrue(String userName);
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptador de la entidad User a Spring Security.
|
||||
*/
|
||||
public class UserDetailsImpl implements UserDetails {
|
||||
|
||||
private final User user;
|
||||
|
||||
public UserDetailsImpl(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
// Si tu User tiene un Set<Role>:
|
||||
Set<String> roles = user.getRoles().stream()
|
||||
.map(r -> r.getName()) // ejemplo: "ADMIN", "USER"
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassword(); // debe estar encriptado (BCrypt)
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getUserName();
|
||||
}
|
||||
|
||||
public String getFullname() {
|
||||
|
||||
return user.getFullName();
|
||||
}
|
||||
|
||||
// 👇 si en la vista usas principal.role, añade este también
|
||||
public String getRole() {
|
||||
return user.getRoles().stream()
|
||||
.map(r -> r.getName()) // "ADMIN", "USER", ...
|
||||
.findFirst()
|
||||
.orElse("-");
|
||||
}
|
||||
|
||||
/** (Opcional) Todos los roles “limpios” por si quieres listarlos. */
|
||||
public java.util.Set<String> getRoleNames() {
|
||||
return user.getRoles().stream()
|
||||
.map(r -> r.getName())
|
||||
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true; // puedes añadir lógica si quieres
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true; // igual que arriba
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return user.isEnabled();
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
public interface UserService extends UserDetailsService {
|
||||
public User findByUserName(String userName);
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.imprimelibros.erp.users;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private UserDao userDao;
|
||||
private RoleDao roleDao;
|
||||
|
||||
public UserServiceImpl(UserDao userDao, RoleDao roleDao) {
|
||||
this.userDao = userDao;
|
||||
this.roleDao = roleDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User findByUserName(String userName) {
|
||||
// check the database if the user already exists
|
||||
return userDao.findByUserNameAndEnabledTrue(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userDao.findByUserNameAndEnabledTrue(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("No existe usuario: " + username);
|
||||
}
|
||||
return new UserDetailsImpl(user);
|
||||
}
|
||||
|
||||
/*public List<User> getUsersList() {
|
||||
return userDao.findAll();
|
||||
}*/
|
||||
|
||||
private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles) {
|
||||
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user