diff --git a/pom.xml b/pom.xml index babd96f8..e34c8b11 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.5 + 3.3.0 @@ -349,7 +349,19 @@ io.swagger.core.v3 swagger-annotations - 2.2.3 + 2.2.22 + + + + co.elastic.clients + elasticsearch-java + 8.13.4 + + + + org.springframework.data + spring-data-elasticsearch + 5.3.0 diff --git a/src/main/java/biblivre/administration/accesscards/AccessCardBO.java b/src/main/java/biblivre/administration/accesscards/AccessCardBO.java index cb6f77c4..e5519d4b 100644 --- a/src/main/java/biblivre/administration/accesscards/AccessCardBO.java +++ b/src/main/java/biblivre/administration/accesscards/AccessCardBO.java @@ -44,6 +44,14 @@ public boolean save(AccessCardDTO dto) { return false; } + public DTOCollection search(PagedAccessCardSearchDTO pagedAccessCardSearchDTO) { + return this.accessCardDAO.search( + pagedAccessCardSearchDTO.code(), + pagedAccessCardSearchDTO.status(), + pagedAccessCardSearchDTO.limit(), + pagedAccessCardSearchDTO.offset()); + } + public DTOCollection search( String code, AccessCardStatus status, int limit, int offset) { return this.accessCardDAO.search(code, status, limit, offset); diff --git a/src/main/java/biblivre/administration/accesscards/Handler.java b/src/main/java/biblivre/administration/accesscards/Handler.java index 735bbe71..e095d48e 100644 --- a/src/main/java/biblivre/administration/accesscards/Handler.java +++ b/src/main/java/biblivre/administration/accesscards/Handler.java @@ -24,55 +24,32 @@ import biblivre.core.ExtendedRequest; import biblivre.core.ExtendedResponse; import biblivre.core.enums.ActionResult; -import biblivre.core.utils.Constants; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.json.JSONException; -import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Handler extends AbstractHandler { private AccessCardBO accessCardBO; + @Autowired private PagedAccessCardSearchWebHelper pagedAccessCardSearchWebHelper; public void search(ExtendedRequest request, ExtendedResponse response) { - DTOCollection list = this.searchHelper(request, response, this); + var pagedAccessCardSearchDTO = + pagedAccessCardSearchWebHelper.getPagedAccessCardSearchDTO(request); - try { - put("search", list.toJSONObject()); - } catch (JSONException e) { - this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); - } - } - - public DTOCollection searchHelper( - ExtendedRequest request, ExtendedResponse response, AbstractHandler handler) { - String searchParameters = request.getString("search_parameters"); + DTOCollection accessCards = accessCardBO.search(pagedAccessCardSearchDTO); - String query; - AccessCardStatus status; - try { - JSONObject json = new JSONObject(searchParameters); - query = json.optString("query"); - status = AccessCardStatus.fromString(json.optString("status")); - } catch (JSONException je) { - this.setMessage(ActionResult.WARNING, "error.invalid_parameters"); - return DTOCollection.empty(); - } - - Integer limit = - request.getInteger( - "limit", configurationBO.getInt(Constants.CONFIG_SEARCH_RESULTS_PER_PAGE)); - int offset = (request.getInteger("page", 1) - 1) * limit; - - DTOCollection list = accessCardBO.search(query, status, limit, offset); - - if (list.size() == 0) { + if (accessCards.isEmpty()) { this.setMessage(ActionResult.WARNING, "administration.accesscards.error.no_card_found"); } - return list; + try { + put("search", accessCards.toJSONObject()); + } catch (JSONException e) { + this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } } public void paginate(ExtendedRequest request, ExtendedResponse response) { diff --git a/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchDTO.java b/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchDTO.java new file mode 100644 index 00000000..ea375223 --- /dev/null +++ b/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchDTO.java @@ -0,0 +1,4 @@ +package biblivre.administration.accesscards; + +public record PagedAccessCardSearchDTO( + String code, AccessCardStatus status, int limit, int offset) {} diff --git a/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchWebHelper.java b/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchWebHelper.java new file mode 100644 index 00000000..4bd6da9e --- /dev/null +++ b/src/main/java/biblivre/administration/accesscards/PagedAccessCardSearchWebHelper.java @@ -0,0 +1,28 @@ +package biblivre.administration.accesscards; + +import biblivre.core.ExtendedRequest; +import biblivre.core.configurations.ConfigurationBO; +import biblivre.core.utils.Constants; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PagedAccessCardSearchWebHelper { + @Autowired private ConfigurationBO configurationBO; + + public PagedAccessCardSearchDTO getPagedAccessCardSearchDTO(ExtendedRequest request) { + String searchParameters = request.getString("search_parameters"); + + JSONObject json = new JSONObject(searchParameters); + String query = json.optString("query"); + var status = AccessCardStatus.fromString(json.optString("status")); + + Integer limit = + request.getInteger( + "limit", configurationBO.getInt(Constants.CONFIG_SEARCH_RESULTS_PER_PAGE)); + int offset = (request.getInteger("page", 1) - 1) * limit; + + return new PagedAccessCardSearchDTO(query, status, limit, offset); + } +} diff --git a/src/main/java/biblivre/administration/indexing/Handler.java b/src/main/java/biblivre/administration/indexing/Handler.java index 4201b6f8..fdef3412 100644 --- a/src/main/java/biblivre/administration/indexing/Handler.java +++ b/src/main/java/biblivre/administration/indexing/Handler.java @@ -20,10 +20,12 @@ package biblivre.administration.indexing; import biblivre.cataloging.enums.RecordType; +import biblivre.circulation.user.UserDAO; import biblivre.core.AbstractHandler; import biblivre.core.ExtendedRequest; import biblivre.core.ExtendedResponse; import biblivre.core.enums.ActionResult; +import biblivre.search.SearchException; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -32,8 +34,9 @@ public class Handler extends AbstractHandler { private IndexingBO indexingBO; - public void reindex(ExtendedRequest request, ExtendedResponse response) { + @Autowired private UserDAO userDAO; + public void reindex(ExtendedRequest request, ExtendedResponse response) throws SearchException { String strRecordType = request.getString("record_type", "biblio"); RecordType recordType = RecordType.fromString(strRecordType); @@ -48,6 +51,7 @@ public void reindex(ExtendedRequest request, ExtendedResponse response) { long end; start = new Date().getTime(); + userDAO.reindexAll(); indexingBO.reindex(recordType); end = new Date().getTime(); diff --git a/src/main/java/biblivre/administration/permissions/Handler.java b/src/main/java/biblivre/administration/permissions/Handler.java index f548bf64..b03e1bab 100644 --- a/src/main/java/biblivre/administration/permissions/Handler.java +++ b/src/main/java/biblivre/administration/permissions/Handler.java @@ -19,6 +19,7 @@ ******************************************************************************/ package biblivre.administration.permissions; +import biblivre.circulation.user.PagedUserSearchWebHelper; import biblivre.circulation.user.UserBO; import biblivre.circulation.user.UserDTO; import biblivre.core.AbstractHandler; @@ -29,6 +30,7 @@ import biblivre.core.utils.TextUtils; import biblivre.login.LoginBO; import biblivre.login.LoginDTO; +import biblivre.search.SearchException; import java.util.Arrays; import java.util.Collection; import org.apache.commons.lang3.StringUtils; @@ -44,25 +46,31 @@ public class Handler extends AbstractHandler { biblivre.circulation.user.Handler userHandler; + @Autowired private PagedUserSearchWebHelper pagedUserSearchWebHelper; + public void search(ExtendedRequest request, ExtendedResponse response) { - DTOCollection userList = userHandler.searchHelper(request, response, this); + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null || userList.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + try { + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + if (userList.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } - for (UserDTO user : userList) { - list.add(this.populatePermission(user)); - } + DTOCollection list = new DTOCollection<>(); + list.setPaging(userList.getPaging()); + + for (UserDTO user : userList) { + list.add(this.populatePermission(user)); + } - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } diff --git a/src/main/java/biblivre/administration/reports/Handler.java b/src/main/java/biblivre/administration/reports/Handler.java index 846891e2..6f8aa19c 100644 --- a/src/main/java/biblivre/administration/reports/Handler.java +++ b/src/main/java/biblivre/administration/reports/Handler.java @@ -21,6 +21,8 @@ import biblivre.cataloging.enums.RecordDatabase; import biblivre.circulation.lending.LendingListDTO; +import biblivre.circulation.user.PagedUserSearchWebHelper; +import biblivre.circulation.user.UserBO; import biblivre.circulation.user.UserDTO; import biblivre.core.AbstractHandler; import biblivre.core.DTOCollection; @@ -29,6 +31,7 @@ import biblivre.core.enums.ActionResult; import biblivre.core.file.DiskFile; import biblivre.core.utils.TextUtils; +import biblivre.search.SearchException; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -36,23 +39,28 @@ @Component public class Handler extends AbstractHandler { private ReportsBO reportsBO; - private biblivre.circulation.user.Handler userHandler; + @Autowired private PagedUserSearchWebHelper pagedUserSearchWebHelper; + @Autowired private UserBO userBO; public void userSearch(ExtendedRequest request, ExtendedResponse response) { - DTOCollection userList = userHandler.searchHelper(request, response, this); + try { + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null || userList.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + if (userList.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } + + DTOCollection list = new DTOCollection<>(); + list.setPaging(userList.getPaging()); - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } @@ -126,9 +134,4 @@ private ReportsDTO populateDto(ExtendedRequest request) throws Exception { public void setReportsBO(ReportsBO reportsBO) { this.reportsBO = reportsBO; } - - @Autowired - public void setUserHandler(biblivre.circulation.user.Handler userHandler) { - this.userHandler = userHandler; - } } diff --git a/src/main/java/biblivre/administration/usertype/Handler.java b/src/main/java/biblivre/administration/usertype/Handler.java index 8b04f65e..f40dc1e9 100644 --- a/src/main/java/biblivre/administration/usertype/Handler.java +++ b/src/main/java/biblivre/administration/usertype/Handler.java @@ -25,6 +25,7 @@ import biblivre.core.ExtendedResponse; import biblivre.core.enums.ActionResult; import biblivre.core.utils.Constants; +import biblivre.search.SearchException; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; @@ -120,10 +121,14 @@ public void save(ExtendedRequest request, ExtendedResponse response) { public void delete(ExtendedRequest request, ExtendedResponse response) { Integer id = request.getInteger("id"); - if (userTypeBO.delete(id)) { - this.setMessage(ActionResult.SUCCESS, "administration.user_type.success.delete"); - } else { - this.setMessage(ActionResult.WARNING, "administration.user_type.error.delete"); + try { + if (userTypeBO.delete(id)) { + this.setMessage(ActionResult.SUCCESS, "administration.user_type.success.delete"); + } else { + this.setMessage(ActionResult.WARNING, "administration.user_type.error.delete"); + } + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } diff --git a/src/main/java/biblivre/administration/usertype/UserTypeBO.java b/src/main/java/biblivre/administration/usertype/UserTypeBO.java index f33d58db..fa189fa7 100644 --- a/src/main/java/biblivre/administration/usertype/UserTypeBO.java +++ b/src/main/java/biblivre/administration/usertype/UserTypeBO.java @@ -25,6 +25,7 @@ import biblivre.core.AbstractBO; import biblivre.core.DTOCollection; import biblivre.core.exceptions.ValidationException; +import biblivre.search.SearchException; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -61,13 +62,13 @@ public boolean save(UserTypeDTO userTypeDTO) { return this.userTypeDAO.save(userTypeDTO); } - public boolean delete(int id) { + public boolean delete(int id) throws SearchException { // Check if there's any user for this user_type UserSearchDTO dto = new UserSearchDTO(); dto.setType(id); DTOCollection userList = userDAO.search(dto, 1, 0); - boolean existingUser = userList.size() > 0; + boolean existingUser = !userList.isEmpty(); if (existingUser) { throw new ValidationException("administration.user_type.error.type_has_users"); diff --git a/src/main/java/biblivre/cataloging/RecordDAOImpl.java b/src/main/java/biblivre/cataloging/RecordDAOImpl.java index 877de6b2..db23c0f8 100644 --- a/src/main/java/biblivre/cataloging/RecordDAOImpl.java +++ b/src/main/java/biblivre/cataloging/RecordDAOImpl.java @@ -506,13 +506,13 @@ public List getSearchResults(SearchDTO search) { } if (useLimit) { - pst.setInt(index++, search.getRecordLimit()); + pst.setLong(index++, search.getRecordLimit()); } pst.setInt(index++, search.getSort()); - pst.setInt(index++, paging.getRecordOffset()); - pst.setInt(index, paging.getRecordsPerPage()); + pst.setLong(index++, paging.getRecordOffset()); + pst.setLong(index, paging.getRecordsPerPage()); ResultSet rs = pst.executeQuery(); diff --git a/src/main/java/biblivre/cataloging/holding/HoldingDAOImpl.java b/src/main/java/biblivre/cataloging/holding/HoldingDAOImpl.java index acb8887e..77f0dff4 100644 --- a/src/main/java/biblivre/cataloging/holding/HoldingDAOImpl.java +++ b/src/main/java/biblivre/cataloging/holding/HoldingDAOImpl.java @@ -789,15 +789,15 @@ public List getSearchResults(SearchDTO search) { } if (useLimit) { - pst.setInt(index++, search.getRecordLimit()); + pst.setLong(index++, search.getRecordLimit()); } index = this.populatePreparedStatement(pst, index, search); pst.setInt(index++, search.getSort()); - pst.setInt(index++, paging.getRecordOffset()); - pst.setInt(index, paging.getRecordsPerPage()); + pst.setLong(index++, paging.getRecordOffset()); + pst.setLong(index, paging.getRecordsPerPage()); ResultSet rs = pst.executeQuery(); diff --git a/src/main/java/biblivre/circulation/accesscontrol/Handler.java b/src/main/java/biblivre/circulation/accesscontrol/Handler.java index 63db0968..b7413053 100644 --- a/src/main/java/biblivre/circulation/accesscontrol/Handler.java +++ b/src/main/java/biblivre/circulation/accesscontrol/Handler.java @@ -21,6 +21,8 @@ import biblivre.administration.accesscards.AccessCardBO; import biblivre.administration.accesscards.AccessCardDTO; +import biblivre.administration.accesscards.PagedAccessCardSearchWebHelper; +import biblivre.circulation.user.PagedUserSearchWebHelper; import biblivre.circulation.user.UserBO; import biblivre.circulation.user.UserDTO; import biblivre.core.AbstractHandler; @@ -28,6 +30,7 @@ import biblivre.core.ExtendedRequest; import biblivre.core.ExtendedResponse; import biblivre.core.enums.ActionResult; +import biblivre.search.SearchException; import java.util.Date; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; @@ -38,50 +41,54 @@ public class Handler extends AbstractHandler { private AccessCardBO accessCardBO; private AccessControlBO accessControlBO; private UserBO userBO; - private biblivre.circulation.user.Handler userHandler; - private biblivre.administration.accesscards.Handler cardHandler; + @Autowired private PagedUserSearchWebHelper pagedUserSearchWebHelper; + @Autowired private PagedAccessCardSearchWebHelper pagedAccessCardSearchWebHelper; public void userSearch(ExtendedRequest request, ExtendedResponse response) { - DTOCollection userList = userHandler.searchHelper(request, response, this); + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null) { - return; - } + try { + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + DTOCollection list = new DTOCollection<>(); - for (UserDTO user : userList) { - AccessControlDTO dto = accessControlBO.getByUserId(user.getId()); - if (dto == null) { - dto = new AccessControlDTO(); - dto.setUserId(user.getId()); - } + list.setPaging(userList.getPaging()); - dto.setId(user.getId()); - dto.setUser(user); + for (UserDTO user : userList) { + AccessControlDTO dto = accessControlBO.getByUserId(user.getId()); + if (dto == null) { + dto = new AccessControlDTO(); + dto.setUserId(user.getId()); + } - if (dto.getAccessCardId() != null) { - dto.setAccessCard(accessCardBO.get(dto.getAccessCardId())); - } + dto.setId(user.getId()); + dto.setUser(user); - list.add(dto); - } + if (dto.getAccessCardId() != null) { + dto.setAccessCard(accessCardBO.get(dto.getAccessCardId())); + } - if (list.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + list.add(dto); + } + + if (list.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } public void cardSearch(ExtendedRequest request, ExtendedResponse response) { - DTOCollection cardList = cardHandler.searchHelper(request, response, this); + var pagedAccessCardSearchDTO = + pagedAccessCardSearchWebHelper.getPagedAccessCardSearchDTO(request); + + DTOCollection cardList = accessCardBO.search(pagedAccessCardSearchDTO); if (cardList == null) { return; @@ -107,7 +114,7 @@ public void cardSearch(ExtendedRequest request, ExtendedResponse response) { list.add(dto); } - if (list.size() == 0) { + if (list.isEmpty()) { this.setMessage(ActionResult.WARNING, "administration.accesscards.error.no_card_found"); return; } @@ -184,14 +191,4 @@ public void setAccessControlBO(AccessControlBO accessControlBO) { public void setUserBO(UserBO userBO) { this.userBO = userBO; } - - @Autowired - public void setUserHandler(biblivre.circulation.user.Handler userHandler) { - this.userHandler = userHandler; - } - - @Autowired - public void setCardHandler(biblivre.administration.accesscards.Handler cardHandler) { - this.cardHandler = cardHandler; - } } diff --git a/src/main/java/biblivre/circulation/lending/Handler.java b/src/main/java/biblivre/circulation/lending/Handler.java index 46b121e4..9299e643 100644 --- a/src/main/java/biblivre/circulation/lending/Handler.java +++ b/src/main/java/biblivre/circulation/lending/Handler.java @@ -28,6 +28,7 @@ import biblivre.cataloging.holding.HoldingBO; import biblivre.cataloging.holding.HoldingDTO; import biblivre.circulation.reservation.ReservationBO; +import biblivre.circulation.user.PagedUserSearchWebHelper; import biblivre.circulation.user.UserBO; import biblivre.circulation.user.UserDTO; import biblivre.core.AbstractHandler; @@ -37,6 +38,7 @@ import biblivre.core.PagingDTO; import biblivre.core.enums.ActionResult; import biblivre.core.utils.Constants; +import biblivre.search.SearchException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -55,7 +57,7 @@ public class Handler extends AbstractHandler { private UserTypeBO userTypeBO; private LendingFineBO lendingFineBO; private ReservationBO reservationBO; - private biblivre.circulation.user.Handler userHandler; + @Autowired private PagedUserSearchWebHelper pagedUserSearchWebHelper; public void search(ExtendedRequest request, ExtendedResponse response) { @@ -96,37 +98,38 @@ public void search(ExtendedRequest request, ExtendedResponse response) { } public void userSearch(ExtendedRequest request, ExtendedResponse response) { - DTOCollection userList = userHandler.searchHelper(request, response, this); + try { + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null || userList.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + if (userList.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } - for (UserDTO user : userList) { - list.add(this.populateLendingList(user, false)); - } + DTOCollection list = new DTOCollection<>(); + list.setPaging(userList.getPaging()); + + for (UserDTO user : userList) { + list.add(this.populateLendingList(user)); + } - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } - private LendingListDTO populateLendingList(UserDTO user, boolean history) { + private LendingListDTO populateLendingList(UserDTO user) { LendingListDTO lendingList = new LendingListDTO(); lendingList.setUser(user); lendingList.setId(user.getId()); List lendings = lendingBO.listUserLendings(user); - if (history) { - lendings.addAll(lendingBO.listHistory(user)); - } List infos = new ArrayList<>(); @@ -287,7 +290,7 @@ public void listAll(ExtendedRequest request, ExtendedResponse response) { PagingDTO paging = new PagingDTO(lendingBO.countLendings(), limit, offset); list.setPaging(paging); - if (list.size() == 0) { + if (list.isEmpty()) { this.setMessage(ActionResult.WARNING, "circulation.lending.no_lending_found"); return; } @@ -318,8 +321,6 @@ public void printReceipt(ExtendedRequest request, ExtendedResponse response) { put("receipt", receipt); } catch (JSONException e) { - e.printStackTrace(); - this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); } } @@ -358,9 +359,4 @@ public void setLendingFineBO(LendingFineBO lendingFineBO) { public void setReservationBO(ReservationBO reservationBO) { this.reservationBO = reservationBO; } - - @Autowired - public void setUserHandler(biblivre.circulation.user.Handler userHandler) { - this.userHandler = userHandler; - } } diff --git a/src/main/java/biblivre/circulation/lending/LendingDAO.java b/src/main/java/biblivre/circulation/lending/LendingDAO.java index 6a77ca8a..3342c148 100644 --- a/src/main/java/biblivre/circulation/lending/LendingDAO.java +++ b/src/main/java/biblivre/circulation/lending/LendingDAO.java @@ -47,4 +47,6 @@ public interface LendingDAO { Integer countLentHoldings(int recordId); LendingDTO getLatest(int holdingSerial, int userId); + + boolean hasLateLendings(int userId); } diff --git a/src/main/java/biblivre/circulation/lending/LendingDAOImpl.java b/src/main/java/biblivre/circulation/lending/LendingDAOImpl.java index 8e64f8df..928449f1 100644 --- a/src/main/java/biblivre/circulation/lending/LendingDAOImpl.java +++ b/src/main/java/biblivre/circulation/lending/LendingDAOImpl.java @@ -411,6 +411,36 @@ public LendingDTO getLatest(int holdingSerial, int userId) { return dto; } + @Override + public boolean hasLateLendings(int userId) { + String sql = + """ + SELECT CASE + WHEN EXISTS ( + SELECT 1 FROM lendings + WHERE user_id = ? AND return_date IS NULL + AND expected_return_date < now() + ) THEN true + ELSE false + """; + + try (Connection con = datasource.getConnection(); + PreparedStatement preparedStatement = con.prepareStatement(sql)) { + + preparedStatement.setInt(1, userId); + + ResultSet rs = preparedStatement.executeQuery(); + + if (!rs.next()) { + throw new RuntimeException("Should never happen"); + } + + return rs.getBoolean(1); + } catch (Exception e) { + throw new DAOException(e); + } + } + private LendingDTO populateDTO(ResultSet rs) throws SQLException { LendingDTO dto = new LendingDTO(); diff --git a/src/main/java/biblivre/circulation/lending/LendingFineDAO.java b/src/main/java/biblivre/circulation/lending/LendingFineDAO.java index 5582c570..635d9054 100644 --- a/src/main/java/biblivre/circulation/lending/LendingFineDAO.java +++ b/src/main/java/biblivre/circulation/lending/LendingFineDAO.java @@ -14,4 +14,6 @@ public interface LendingFineDAO { List list(UserDTO user, boolean pendingOnly); boolean update(LendingFineDTO fine); + + boolean hasPendingFine(int userID); } diff --git a/src/main/java/biblivre/circulation/lending/LendingFineDAOImpl.java b/src/main/java/biblivre/circulation/lending/LendingFineDAOImpl.java index 4cccd571..83d16465 100644 --- a/src/main/java/biblivre/circulation/lending/LendingFineDAOImpl.java +++ b/src/main/java/biblivre/circulation/lending/LendingFineDAOImpl.java @@ -144,6 +144,38 @@ public boolean update(LendingFineDTO fine) { } } + @Override + public boolean hasPendingFine(int userID) { + try (Connection con = datasource.getConnection()) { + + String sql = + """ + SELECT CASE + WHEN EXISTS ( + SELECT 1 FROM lending_fines + WHERE user_id = ? AND payment_date IS NULL + ) THEN true + ELSE false + END + """; + + PreparedStatement pst = con.prepareStatement(sql); + + pst.setInt(1, userID); + + ResultSet rs = pst.executeQuery(); + + if (rs.next()) { + return rs.getBoolean(1); + } + + } catch (Exception e) { + throw new DAOException(e); + } + + return false; + } + private LendingFineDTO populateDTO(ResultSet rs) throws SQLException { LendingFineDTO dto = new LendingFineDTO(); diff --git a/src/main/java/biblivre/circulation/reservation/Handler.java b/src/main/java/biblivre/circulation/reservation/Handler.java index 46e8ba56..aec3b30f 100644 --- a/src/main/java/biblivre/circulation/reservation/Handler.java +++ b/src/main/java/biblivre/circulation/reservation/Handler.java @@ -28,6 +28,7 @@ import biblivre.cataloging.enums.RecordType; import biblivre.cataloging.search.SearchDTO; import biblivre.cataloging.search.SearchQueryDTO; +import biblivre.circulation.user.PagedUserSearchWebHelper; import biblivre.circulation.user.UserBO; import biblivre.circulation.user.UserDTO; import biblivre.circulation.user.UserSearchDTO; @@ -37,6 +38,7 @@ import biblivre.core.ExtendedResponse; import biblivre.core.auth.AuthorizationPoints; import biblivre.core.enums.ActionResult; +import biblivre.search.SearchException; import java.util.List; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; @@ -50,6 +52,7 @@ public class Handler extends AbstractHandler { private ReservationBO reservationBO; private biblivre.circulation.user.Handler userHandler; private IndexingGroupBO indexingGroupBO; + @Autowired private PagedUserSearchWebHelper pagedUserSearchWebHelper; public void search(ExtendedRequest request, ExtendedResponse response) { String searchParameters = request.getString("search_parameters"); @@ -62,7 +65,7 @@ public void search(ExtendedRequest request, ExtendedResponse response) { SearchDTO search = biblioRecordBO.search(searchQuery, authorizationPoints); - if (search.size() == 0) { + if (search.isEmpty()) { this.setMessage(ActionResult.WARNING, "cataloging.error.no_records_found"); } @@ -118,24 +121,28 @@ public void paginate(ExtendedRequest request, ExtendedResponse response) { } public void userSearch(ExtendedRequest request, ExtendedResponse response) { - DTOCollection userList = userHandler.searchHelper(request, response, this); + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null || userList.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + try { + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + if (userList.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } - for (UserDTO user : userList) { - list.add(this.populateReservationList(user)); - } + DTOCollection list = new DTOCollection<>(); + list.setPaging(userList.getPaging()); + + for (UserDTO user : userList) { + list.add(this.populateReservationList(user)); + } - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } @@ -202,7 +209,7 @@ public void selfSearch(ExtendedRequest request, ExtendedResponse response) { SearchDTO search = biblioRecordBO.search(searchQuery, authorizationPoints); - if (search.size() == 0) { + if (search.isEmpty()) { this.setMessage(ActionResult.WARNING, "cataloging.error.no_records_found"); } @@ -269,24 +276,28 @@ public void selfOpen(ExtendedRequest request, ExtendedResponse response) { return; } - DTOCollection userList = userHandler.searchHelper(request, response, this); + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); - if (userList == null || userList.size() == 0) { - this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - return; - } + try { + DTOCollection userList = userBO.search(pagedSearchDTO); - DTOCollection list = new DTOCollection<>(); - list.setPaging(userList.getPaging()); + if (userList.isEmpty()) { + this.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + return; + } - for (UserDTO user : userList) { - list.add(this.populateReservationList(user)); - } + DTOCollection list = new DTOCollection<>(); + list.setPaging(userList.getPaging()); + + for (UserDTO user : userList) { + list.add(this.populateReservationList(user)); + } - try { put("search", list.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } diff --git a/src/main/java/biblivre/circulation/user/Handler.java b/src/main/java/biblivre/circulation/user/Handler.java index 0b69625d..e933042b 100644 --- a/src/main/java/biblivre/circulation/user/Handler.java +++ b/src/main/java/biblivre/circulation/user/Handler.java @@ -33,9 +33,9 @@ import biblivre.core.ExtendedResponse; import biblivre.core.enums.ActionResult; import biblivre.core.file.MemoryFile; -import biblivre.core.utils.Constants; import biblivre.digitalmedia.DigitalMediaBO; import biblivre.digitalmedia.DigitalMediaEncodingUtil; +import biblivre.search.SearchException; import java.io.ByteArrayInputStream; import java.util.Base64; import java.util.Collection; @@ -54,6 +54,7 @@ public class Handler extends AbstractHandler { private ReservationBO reservationBO; private DigitalMediaBO digitalMediaBO; private UserFieldBO userFieldBO; + private PagedUserSearchWebHelper pagedUserSearchWebHelper; public void open(ExtendedRequest request, ExtendedResponse response) { Integer id = request.getInteger("id"); @@ -73,12 +74,21 @@ public void open(ExtendedRequest request, ExtendedResponse response) { } public void search(ExtendedRequest request, ExtendedResponse response) { - DTOCollection list = this.searchHelper(request, response, this); - try { - put("search", list.toJSONObject()); + + var pagedSearchDTO = pagedUserSearchWebHelper.getPagedUserSearchDTO(request); + + DTOCollection userList = userBO.search(pagedSearchDTO); + + if (userList.isEmpty()) { + setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); + } + + put("search", userList.toJSONObject()); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "circulation.error.search_error"); } } @@ -86,32 +96,6 @@ public void paginate(ExtendedRequest request, ExtendedResponse response) { this.search(request, response); } - public DTOCollection searchHelper( - ExtendedRequest request, ExtendedResponse response, AbstractHandler handler) { - - String searchParameters = request.getString("search_parameters"); - - UserSearchDTO searchDto = new UserSearchDTO(searchParameters); - - Integer limit = - request.getInteger( - "limit", configurationBO.getInt(Constants.CONFIG_SEARCH_RESULTS_PER_PAGE)); - Integer offset = request.getInteger("offset", 0); - - Integer page = request.getInteger("page", 1); - if (page > 1) { - offset = limit * (page - 1); - } - - DTOCollection list = userBO.search(searchDto, limit, offset); - - if (list.size() == 0) { - handler.setMessage(ActionResult.WARNING, "circulation.error.no_users_found"); - } - - return list; - } - public void save(ExtendedRequest request, ExtendedResponse response) { Integer id = request.getInteger("id"); @@ -178,21 +162,25 @@ public void save(ExtendedRequest request, ExtendedResponse response) { } } - if (userBO.save(user)) { - if (id == 0) { - this.setMessage(ActionResult.SUCCESS, "circulation.users.success.save"); + try { + UserDTO saved = userBO.save(user); + + if (saved != null) { + if (id == 0) { + this.setMessage(ActionResult.SUCCESS, "circulation.users.success.saved"); + } else { + this.setMessage(ActionResult.SUCCESS, "circulation.users.success.update"); + } + + put("data", saved.toJSONObject()); + put("full_data", true); } else { - this.setMessage(ActionResult.SUCCESS, "circulation.users.success.update"); + this.setMessage(ActionResult.WARNING, "circulation.users.error.saved"); } - } else { - this.setMessage(ActionResult.WARNING, "circulation.users.error.save"); - } - - try { - put("data", user.toJSONObject()); - put("full_data", true); } catch (JSONException e) { this.setMessage(ActionResult.WARNING, ERROR_INVALID_JSON); + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } @@ -201,14 +189,24 @@ public void delete(ExtendedRequest request, ExtendedResponse response) { UserDTO user = userBO.get(id); - String act = (user.getStatus() == UserStatus.INACTIVE) ? "delete" : "disable"; + String action = (user.getStatus() == UserStatus.INACTIVE) ? "delete" : "disable"; - boolean success = userBO.delete(user); + try { + boolean success = false; - if (success) { - this.setMessage(ActionResult.SUCCESS, "circulation.users.success." + act); - } else { - this.setMessage(ActionResult.WARNING, "circulation.users.failure." + act); + if ("delete".equals(action)) { + success = userBO.delete(user); + } else { + success = userBO.updateUserStatus(id, UserStatus.INACTIVE); + } + + if (success) { + this.setMessage(ActionResult.SUCCESS, "circulation.users.success." + action); + } else { + this.setMessage(ActionResult.WARNING, "circulation.users.failure." + action); + } + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "circulation.users.failure." + action); } } @@ -260,22 +258,30 @@ public void loadTabData(ExtendedRequest request, ExtendedResponse response) { public void block(ExtendedRequest request, ExtendedResponse response) { Integer userId = request.getInteger("user_id"); - boolean success = userBO.updateUserStatus(userId, UserStatus.BLOCKED); - if (success) { - this.setMessage(ActionResult.SUCCESS, "circulation.users.success.block"); - } else { - this.setMessage(ActionResult.WARNING, "circulation.users.failure.block"); + try { + boolean success = userBO.updateUserStatus(userId, UserStatus.BLOCKED); + if (success) { + this.setMessage(ActionResult.SUCCESS, "circulation.users.success.block"); + } else { + this.setMessage(ActionResult.WARNING, "circulation.users.failure.block"); + } + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } public void unblock(ExtendedRequest request, ExtendedResponse response) { Integer userId = request.getInteger("user_id"); - boolean success = userBO.updateUserStatus(userId, UserStatus.ACTIVE); - if (success) { - this.setMessage(ActionResult.SUCCESS, "circulation.users.success.unblock"); - } else { - this.setMessage(ActionResult.WARNING, "circulation.users.failure.unblock"); + try { + boolean success = userBO.updateUserStatus(userId, UserStatus.ACTIVE); + if (success) { + this.setMessage(ActionResult.SUCCESS, "circulation.users.success.unblock"); + } else { + this.setMessage(ActionResult.WARNING, "circulation.users.failure.unblock"); + } + } catch (SearchException e) { + this.setMessage(ActionResult.ERROR, "error.internal_error"); } } @@ -308,4 +314,9 @@ public void setDigitalMediaBO(DigitalMediaBO digitalMediaBO) { public void setUserFieldBO(UserFieldBO userFieldBO) { this.userFieldBO = userFieldBO; } + + @Autowired + public void setPagedUserSearchWebHelper(PagedUserSearchWebHelper pagedUserSearchWebHelper) { + this.pagedUserSearchWebHelper = pagedUserSearchWebHelper; + } } diff --git a/src/main/java/biblivre/circulation/user/IndexableUserDAO.java b/src/main/java/biblivre/circulation/user/IndexableUserDAO.java new file mode 100644 index 00000000..29bf3162 --- /dev/null +++ b/src/main/java/biblivre/circulation/user/IndexableUserDAO.java @@ -0,0 +1,296 @@ +package biblivre.circulation.user; + +import biblivre.circulation.lending.LendingDAO; +import biblivre.circulation.lending.LendingFineDAO; +import biblivre.core.DTOCollection; +import biblivre.core.PagingDTO; +import biblivre.core.SchemaThreadLocal; +import biblivre.search.SearchException; +import biblivre.search.user.IndexableUser; +import biblivre.search.user.IndexableUserQueryParameters; +import biblivre.search.user.IndexableUserRepository; +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.util.ObjectBuilder; +import jakarta.annotation.Nonnull; +import java.util.*; +import java.util.function.Function; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnProperty(value = "elasticsearch.server.url") +public class IndexableUserDAO extends UserDAOImpl { + @Autowired private IndexableUserRepository indexableUserRepository; + + @Value(value = "${biblivre.cloud.tentant:localhost}") + private String tenant; + + @Autowired private LendingFineDAO lendingFineDAO; + + @Autowired private LendingDAO lendingDAO; + + @Autowired private ElasticsearchOperations elasticsearchOperations; + + @Override + public @Nonnull DTOCollection search(UserSearchDTO dto, int limit, int offset) + throws SearchException { + List filterQueries = getFilterQueries(dto); + + List mustQueries = getMustQueries(dto); + + NativeQuery nativeQuery = + NativeQuery.builder() + .withQuery( + query -> + query.bool( + bool -> + bool.filter(filterQueries) + .must(mustQueries))) + .withPageable(PageRequest.of(offset / limit, limit)) + .build(); + + SearchHits searchHits = + elasticsearchOperations.search(nativeQuery, IndexableUser.class); + + DTOCollection result = + searchHits.getSearchHits().stream() + .map(hit -> getUserDTO(hit.getContent())) + .collect(DTOCollection::new, DTOCollection::add, DTOCollection::addAll); + + result.setPaging(new PagingDTO(searchHits.getTotalHits(), limit, offset)); + + return result; + } + + private List getFilterQueries(UserSearchDTO dto) { + List baseFilterQuries = buildBaseQueries(dto); + + if (dto.isSimpleSearch()) { + return baseFilterQuries; + } + + List filterQueries = new ArrayList<>(baseFilterQuries); + + if (dto.isUserCardNeverPrinted()) { + filterQueries.add(buildBooleanTermQuery("userCardPrinted", false)); + } + + if (dto.isPendingFines()) { + filterQueries.add(buildBooleanTermQuery("hasPendingFines", true)); + } + + if (dto.isInactiveOnly()) { + filterQueries.add(buildBooleanTermQuery("isInactive", true)); + } + + if (dto.isLateLendings()) { + filterQueries.add(buildBooleanTermQuery("hasPendingLoans", true)); + } + + if (dto.isLoginAccess()) { + filterQueries.add(buildBooleanTermQuery("hasLogin", true)); + } + + if (dto.getCreatedStartDate() != null) { + filterQueries.add(buildRangeGteQuery("created", dto.getCreatedStartDate())); + } + + if (dto.getCreatedEndDate() != null) { + filterQueries.add(buildRangeLteQuery("created", dto.getCreatedEndDate())); + } + + if (dto.getModifiedStartDate() != null) { + filterQueries.add(buildRangeGteQuery("modified", dto.getModifiedStartDate())); + } + + if (dto.getModifiedEndDate() != null) { + filterQueries.add(buildRangeLteQuery("modified", dto.getModifiedEndDate())); + } + + if (dto.isSearchById()) { + filterQueries.add(buildLongTermQuery(dto)); + } + + return Collections.unmodifiableList(filterQueries); + } + + private List buildBaseQueries(UserSearchDTO dto) { + Query schemaQ = buildStringTermQuery("schema", SchemaThreadLocal.get()); + + Query tenantQ = buildStringTermQuery("tenant", tenant); + + return List.of(schemaQ, tenantQ); + } + + private static List getMustQueries(UserSearchDTO dto) { + return dto.isListAll() ? Collections.emptyList() : List.of(getMustQueryForName(dto)); + } + + private static Query getMustQueryForName(UserSearchDTO dto) { + return new Query.Builder() + .match(match -> match.field("name").query(dto.getQuery()).fuzziness("AUTO")) + .build(); + } + + private static Query buildLongTermQuery(UserSearchDTO dto) { + return buildTermQuery("id", value -> value.longValue(Long.parseLong(dto.getQuery()))); + } + + private static Query buildRangeGteQuery(String field, Date value) { + return new Query.Builder() + .range(range -> range.field(field).gte(JsonData.of(value))) + .build(); + } + + private static Query buildRangeLteQuery(String field, Date value) { + return new Query.Builder() + .range(range -> range.field(field).lte(JsonData.of(value))) + .build(); + } + + private static Query buildBooleanTermQuery(String field, boolean value) { + return buildTermQuery(field, v -> v.booleanValue(value)); + } + + private static Query buildStringTermQuery(String field, String value) { + return buildTermQuery(field, v -> v.stringValue(value)); + } + + private static Query buildTermQuery( + String field, Function> valueFunction) { + return new Query.Builder().term(term -> term.field(field).value(valueFunction)).build(); + } + + private IndexableUserQueryParameters getIndexableUserQueryParameters(UserSearchDTO userSearch) { + return new IndexableUserQueryParameters( + parseInt(userSearch.getQuery()).orElse(-1), + userSearch.getQuery(), + SchemaThreadLocal.get(), + tenant, + userSearch.isLoginAccess(), + userSearch.isPendingFines(), + userSearch.isLateLendings(), + userSearch.isUserCardNeverPrinted(), + userSearch.isInactiveOnly()); + } + + @Override + public UserDTO save(UserDTO user) throws SearchException { + UserDTO saved = super.save(user); + + index(user); + + return saved; + } + + private void index(UserDTO user) throws SearchException { + indexableUserRepository.save(getEsUser(user)); + } + + @Override + public boolean delete(UserDTO user) throws SearchException { + super.delete(user); + + indexableUserRepository.delete(getEsUser(user)); + + return true; + } + + @Override + public void markAsPrinted(Collection ids) throws SearchException { + super.markAsPrinted(ids); + + reindex(ids); + } + + @Override + public boolean updateUserStatus(Integer userId, UserStatus status) throws SearchException { + super.updateUserStatus(userId, status); + + reindex(Set.of(userId)); + + return true; + } + + @Override + public void reindexAll() throws SearchException { + indexableUserRepository.deleteBySchemaAndTenant(SchemaThreadLocal.get(), tenant); + + DTOCollection allUsers = super.search(new UserSearchDTO(), Integer.MAX_VALUE, 0); + + bulkIndex(allUsers.stream().map(this::getEsUser).toList()); + } + + private void reindex(Collection ids) throws SearchException { + Map usersMap = super.map(ids); + + bulkIndex(usersMap.values().stream().map(this::getEsUser).toList()); + } + + private void bulkIndex(Collection users) { + indexableUserRepository.saveAll(users); + } + + private IndexableUser getEsUser(UserDTO user) { + int userId = user.getId(); + + boolean userHasPendingFines = lendingFineDAO.hasPendingFine(userId); + + boolean userHasPendingLoans = lendingDAO.hasLateLendings(userId); + + return new IndexableUser( + userId, + user.getName(), + user.getType(), + user.getPhotoId(), + user.getStatus().toString(), + user.getLoginId() == null ? -1 : user.getLoginId(), + user.getCreated(), + user.getCreatedBy(), + user.getModified(), + user.getModifiedBy(), + user.isUserCardPrinted(), + user.getFields(), + userHasPendingFines, + userHasPendingLoans, + user.hasLogin(), + user.isInactive(), + SchemaThreadLocal.get(), + tenant); + } + + private static UserDTO getUserDTO(IndexableUser indexableUser) { + UserDTO userDTO = new UserDTO(); + + userDTO.setId(indexableUser.getId()); + userDTO.setName(indexableUser.getName()); + userDTO.setType(indexableUser.getType()); + userDTO.setPhotoId(indexableUser.getPhotoId()); + userDTO.setStatus(UserStatus.fromString(indexableUser.getStatus())); + userDTO.setLoginId(indexableUser.getLoginId() == -1 ? null : indexableUser.getLoginId()); + userDTO.setCreatedBy(indexableUser.getCreatedBy()); + userDTO.setCreated(indexableUser.getCreated()); + userDTO.setModifiedBy(indexableUser.getModifiedBy()); + userDTO.setModified(indexableUser.getModified()); + userDTO.setUserCardPrinted(indexableUser.isUserCardPrinted()); + userDTO.setFields(indexableUser.getFields()); + + return userDTO; + } + + private static OptionalInt parseInt(String query) { + try { + return OptionalInt.of(Integer.parseInt(query)); + } catch (NumberFormatException e) { + return OptionalInt.empty(); + } + } +} diff --git a/src/main/java/biblivre/circulation/user/PagedUserSearchDTO.java b/src/main/java/biblivre/circulation/user/PagedUserSearchDTO.java new file mode 100644 index 00000000..883ea1dd --- /dev/null +++ b/src/main/java/biblivre/circulation/user/PagedUserSearchDTO.java @@ -0,0 +1,3 @@ +package biblivre.circulation.user; + +public record PagedUserSearchDTO(UserSearchDTO userSearchDTO, int limit, int offset) {} diff --git a/src/main/java/biblivre/circulation/user/PagedUserSearchWebHelper.java b/src/main/java/biblivre/circulation/user/PagedUserSearchWebHelper.java new file mode 100644 index 00000000..f5dce56a --- /dev/null +++ b/src/main/java/biblivre/circulation/user/PagedUserSearchWebHelper.java @@ -0,0 +1,32 @@ +package biblivre.circulation.user; + +import biblivre.core.ExtendedRequest; +import biblivre.core.configurations.ConfigurationBO; +import biblivre.core.utils.Constants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PagedUserSearchWebHelper { + @Autowired private ConfigurationBO configurationBO; + + public PagedUserSearchDTO getPagedUserSearchDTO(ExtendedRequest request) { + String searchParameters = request.getString("search_parameters"); + + UserSearchDTO searchDto = new UserSearchDTO(searchParameters); + + Integer limit = + request.getInteger( + "limit", configurationBO.getInt(Constants.CONFIG_SEARCH_RESULTS_PER_PAGE)); + + Integer offset = request.getInteger("offset", 0); + + Integer page = request.getInteger("page", 1); + + if (page > 1) { + offset = limit * (page - 1); + } + + return new PagedUserSearchDTO(searchDto, limit, offset); + } +} diff --git a/src/main/java/biblivre/circulation/user/UserBO.java b/src/main/java/biblivre/circulation/user/UserBO.java index 2c686f0b..fa40a982 100644 --- a/src/main/java/biblivre/circulation/user/UserBO.java +++ b/src/main/java/biblivre/circulation/user/UserBO.java @@ -27,6 +27,7 @@ import biblivre.core.translations.TranslationsMap; import biblivre.core.utils.Constants; import biblivre.labels.print.LabelPrintDTO; +import biblivre.search.SearchException; import com.lowagie.text.Chunk; import com.lowagie.text.Document; import com.lowagie.text.Element; @@ -40,6 +41,7 @@ import com.lowagie.text.pdf.PdfPCell; import com.lowagie.text.pdf.PdfPTable; import com.lowagie.text.pdf.PdfWriter; +import jakarta.annotation.Nonnull; import java.io.File; import java.io.OutputStream; import java.nio.file.Files; @@ -56,7 +58,16 @@ public class UserBO extends AbstractBO { private UserDAO userDAO; private UserTypeBO userTypeBO; - public DTOCollection search(UserSearchDTO dto, int limit, int offset) { + public @Nonnull DTOCollection search(PagedUserSearchDTO pagedUserSearchDTO) + throws SearchException { + return this.search( + pagedUserSearchDTO.userSearchDTO(), + pagedUserSearchDTO.limit(), + pagedUserSearchDTO.offset()); + } + + public @Nonnull DTOCollection search(UserSearchDTO dto, int limit, int offset) + throws SearchException { DTOCollection list = this.userDAO.search(dto, limit, offset); Map map = userTypeBO.map(); @@ -99,15 +110,15 @@ public UserDTO getUserByLoginId(Integer loginId) { return this.get(userId); } - public boolean save(UserDTO user) { + public UserDTO save(UserDTO user) throws SearchException { return this.userDAO.save(user); } - public boolean updateUserStatus(Integer userId, UserStatus status) { + public boolean updateUserStatus(Integer userId, UserStatus status) throws SearchException { return this.userDAO.updateUserStatus(userId, status); } - public boolean delete(UserDTO user) { + public boolean delete(UserDTO user) throws SearchException { return this.userDAO.delete(user); } @@ -221,7 +232,7 @@ private String getText(TranslationsMap i18n, String key) { return text; } - public void markAsPrinted(Set ids) { + public void markAsPrinted(Set ids) throws SearchException { this.userDAO.markAsPrinted(ids); } diff --git a/src/main/java/biblivre/circulation/user/UserDAO.java b/src/main/java/biblivre/circulation/user/UserDAO.java index 948ecf95..177d44a7 100644 --- a/src/main/java/biblivre/circulation/user/UserDAO.java +++ b/src/main/java/biblivre/circulation/user/UserDAO.java @@ -1,22 +1,25 @@ package biblivre.circulation.user; import biblivre.core.DTOCollection; +import biblivre.search.SearchException; +import java.util.Collection; import java.util.Map; -import java.util.Set; public interface UserDAO { - Map map(Set ids); + Map map(Collection ids); - DTOCollection search(UserSearchDTO dto, int limit, int offset); + DTOCollection search(UserSearchDTO dto, int limit, int offset) throws SearchException; - boolean save(UserDTO user); + UserDTO save(UserDTO user) throws SearchException; - boolean delete(UserDTO user); + boolean delete(UserDTO user) throws SearchException; - void markAsPrinted(Set ids); + void markAsPrinted(Collection ids) throws SearchException; - boolean updateUserStatus(Integer userId, UserStatus status); + boolean updateUserStatus(Integer userId, UserStatus status) throws SearchException; Integer getUserIdByLoginId(Integer loginId); + + default void reindexAll() throws SearchException {} } diff --git a/src/main/java/biblivre/circulation/user/UserDAOImpl.java b/src/main/java/biblivre/circulation/user/UserDAOImpl.java index e3a73e20..0da59c6f 100644 --- a/src/main/java/biblivre/circulation/user/UserDAOImpl.java +++ b/src/main/java/biblivre/circulation/user/UserDAOImpl.java @@ -24,28 +24,25 @@ import biblivre.core.function.UnsafeFunction; import biblivre.core.utils.CalendarUtils; import biblivre.core.utils.TextUtils; +import biblivre.search.SearchException; +import jakarta.annotation.Nonnull; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service @Slf4j -public class UserDAOImpl extends AbstractDAO implements UserDAO { +public abstract class UserDAOImpl extends AbstractDAO implements UserDAO { @Override - public Map map(Set ids) { + public Map map(Collection ids) { Map map = new HashMap<>(); try (Connection con = datasource.getConnection()) { @@ -76,7 +73,8 @@ public Map map(Set ids) { } @Override - public DTOCollection search(UserSearchDTO dto, int limit, int offset) { + public @Nonnull DTOCollection search(UserSearchDTO dto, int limit, int offset) + throws SearchException { DTOCollection list = new DTOCollection<>(); String query = dto.getQuery(); @@ -265,7 +263,7 @@ public DTOCollection search(UserSearchDTO dto, int limit, int offset) { } @Override - public boolean save(UserDTO user) { + public UserDTO save(UserDTO user) throws SearchException { try (Connection con = datasource.getConnection()) { con.setAutoCommit(false); @@ -276,8 +274,8 @@ public boolean save(UserDTO user) { if (newUser) { user.setId(this.getNextSerial("users_id_seq")); sql.append( - "INSERT INTO users (id, name, type, photo_id, status, created_by, name_ascii) "); - sql.append("VALUES (?, ?, ?, ?, ?, ?, ?);"); + "INSERT INTO users (id, name, type, photo_id, status, created_by, name_ascii, created) "); + sql.append("VALUES (?, ?, ?, ?, ?, ?, ?, now());"); pst = con.prepareStatement(sql.toString()); pst.setInt(1, user.getId()); pst.setString(2, user.getName()); @@ -297,7 +295,7 @@ public boolean save(UserDTO user) { pst.setString(3, user.getStatus().toString()); pst.setString(4, user.getName()); pst.setInt(5, user.getCreatedBy()); - pst.setBoolean(6, user.getUserCardPrinted()); + pst.setBoolean(6, user.isUserCardPrinted()); pst.setString(7, TextUtils.removeDiacriticals(user.getName())); pst.setInt(8, user.getId()); } @@ -312,14 +310,14 @@ public boolean save(UserDTO user) { con.commit(); - return true; + return map(Set.of(user.getId())).get(user.getId()); } catch (Exception e) { throw new DAOException(e); } } @Override - public boolean delete(UserDTO user) { + public boolean delete(UserDTO user) throws SearchException { return withTransactionContext( (UnsafeFunction) connection -> doDelete(connection, user)); } @@ -384,7 +382,7 @@ private UserDTO populateDTO(ResultSet rs) throws SQLException { dto.setName(rs.getString("name")); dto.setType(rs.getInt("type")); dto.setPhotoId(rs.getString("photo_id")); - dto.setStatus(rs.getString("status")); + dto.setStatus(UserStatus.fromString(rs.getString("status"))); dto.setLoginId(rs.getInt("login_id")); dto.setCreated(rs.getTimestamp("created")); dto.setCreatedBy(rs.getInt("created_by")); @@ -405,7 +403,7 @@ private UserDTO populateDTO(ResultSet rs) throws SQLException { } @Override - public void markAsPrinted(Set ids) { + public void markAsPrinted(Collection ids) throws SearchException { try (Connection con = datasource.getConnection()) { String sql = "UPDATE users SET user_card_printed = true " @@ -426,7 +424,7 @@ public void markAsPrinted(Set ids) { } @Override - public boolean updateUserStatus(Integer userId, UserStatus status) { + public boolean updateUserStatus(Integer userId, UserStatus status) throws SearchException { try (Connection con = datasource.getConnection()) { String sql = "UPDATE users SET status = ? " + "WHERE id = ?;"; diff --git a/src/main/java/biblivre/circulation/user/UserDTO.java b/src/main/java/biblivre/circulation/user/UserDTO.java index 7a1c4764..84558391 100644 --- a/src/main/java/biblivre/circulation/user/UserDTO.java +++ b/src/main/java/biblivre/circulation/user/UserDTO.java @@ -23,9 +23,14 @@ import java.io.Serial; import java.util.HashMap; import java.util.Map; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; import org.json.JSONObject; +@Setter +@Getter public class UserDTO extends AbstractDTO { @Serial private static final long serialVersionUID = 1L; @@ -35,7 +40,7 @@ public class UserDTO extends AbstractDTO { private String photoId; private UserStatus status; private Integer loginId; - private Boolean userCardPrinted; + private boolean isUserCardPrinted; private Map fields; @@ -46,96 +51,20 @@ public UserDTO() { this.fields = new HashMap<>(); } - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - public String getEnrollment() { return StringUtils.leftPad(String.valueOf(this.getId()), 5, "0"); } - public int getType() { - return this.type; - } - - public void setType(Integer type) { - this.type = type; - } - - public String getPhotoId() { - return this.photoId; - } - - public void setPhotoId(String photoId) { - this.photoId = photoId; - } - - public UserStatus getStatus() { - return this.status; - } - - public void setStatus(UserStatus status) { - this.status = status; - } - - public void setStatus(String status) { - this.status = UserStatus.fromString(status); - } - - public Integer getLoginId() { - return this.loginId; - } - - public void setLoginId(Integer loginId) { - this.loginId = loginId; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getFields() { - return this.fields; - } - - public void setFields(HashMap fields) { - this.fields = fields; - } - public void addField(String key, String value) { this.fields.put(key, value); } - public Integer getCurrentLendings() { - return this.currentLendings; - } - - public void setCurrentLendings(Integer currentLendings) { - this.currentLendings = currentLendings; - } - - public Boolean getUserCardPrinted() { - return this.userCardPrinted == null ? Boolean.FALSE : this.userCardPrinted; - } - - public void setUserCardPrinted(Boolean userCardPrinted) { - this.userCardPrinted = userCardPrinted; - } - - public String getUsertypeName() { - return this.usertypeName; + public boolean isInactive() { + return this.getStatus() == UserStatus.INACTIVE; } - public void setUsertypeName(String usertypeName) { - this.usertypeName = usertypeName; + public boolean hasLogin() { + return loginId != null && loginId > 0; } @Override @@ -153,8 +82,11 @@ public JSONObject toJSONObject() { json.putOpt("fields", this.getFields()); json.putOpt("current_lendings", this.getCurrentLendings()); - json.putOpt("created", this.getCreated()); - json.putOpt("modified", this.getModified()); + json.putOpt( + "created", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(getCreated())); + json.putOpt( + "modified", + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(getModified())); return json; } diff --git a/src/main/java/biblivre/circulation/user/UserSearchDTO.java b/src/main/java/biblivre/circulation/user/UserSearchDTO.java index 650f3bb9..ad638e8f 100644 --- a/src/main/java/biblivre/circulation/user/UserSearchDTO.java +++ b/src/main/java/biblivre/circulation/user/UserSearchDTO.java @@ -26,22 +26,25 @@ import java.io.Serial; import java.text.ParseException; import java.util.Date; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; +@Getter public final class UserSearchDTO extends AbstractDTO { @Serial private static final long serialVersionUID = 1L; - private SearchMode searchMode; - private String field; - private String query; - private Integer type; - private boolean pendingFines; - private boolean lateLendings; - private boolean loginAccess; - private boolean userCardNeverPrinted; - private boolean inactiveOnly; + @Setter private SearchMode searchMode; + @Setter private String field; + @Setter private String query; + @Setter private Integer type; + @Setter private boolean isPendingFines; + @Setter private boolean isLateLendings; + @Setter private boolean isLoginAccess; + @Setter private boolean isUserCardNeverPrinted; + @Setter private boolean isInactiveOnly; private Date createdStartDate; private Date createdEndDate; private Date modifiedStartDate; @@ -60,7 +63,7 @@ public UserSearchDTO(String jsonString) throws ValidationException { private void fromJson(String jsonString) throws JSONException, ParseException { JSONObject json = new JSONObject(jsonString); - this.setSearchMode(SearchMode.fromString(json.optString("search_mode"))); + this.setSearchMode(SearchMode.fromString(json.optString("mode"))); this.setField(json.optString("field")); this.setQuery(json.optString("query")); this.setType(json.optInt("type")); @@ -75,82 +78,6 @@ private void fromJson(String jsonString) throws JSONException, ParseException { this.setModifiedEndDate(json.optString("modified_end")); } - public SearchMode getSearchMode() { - return this.searchMode; - } - - public void setSearchMode(SearchMode searchMode) { - this.searchMode = searchMode; - } - - public String getField() { - return this.field; - } - - public void setField(String field) { - this.field = field; - } - - public String getQuery() { - return this.query; - } - - public void setQuery(String query) { - this.query = query; - } - - public Integer getType() { - return this.type; - } - - public void setType(Integer type) { - this.type = type; - } - - public boolean isPendingFines() { - return this.pendingFines; - } - - public void setPendingFines(boolean pendingFines) { - this.pendingFines = pendingFines; - } - - public boolean isLateLendings() { - return this.lateLendings; - } - - public void setLateLendings(boolean lateLendings) { - this.lateLendings = lateLendings; - } - - public boolean isLoginAccess() { - return this.loginAccess; - } - - public void setLoginAccess(boolean loginAccess) { - this.loginAccess = loginAccess; - } - - public boolean isUserCardNeverPrinted() { - return this.userCardNeverPrinted; - } - - public void setUserCardNeverPrinted(boolean userCardNeverPrinted) { - this.userCardNeverPrinted = userCardNeverPrinted; - } - - public boolean isInactiveOnly() { - return this.inactiveOnly; - } - - public void setInactiveOnly(boolean inactiveOnly) { - this.inactiveOnly = inactiveOnly; - } - - public Date getCreatedStartDate() { - return this.createdStartDate; - } - public void setCreatedStartDate(String createdStartDate) throws ParseException { this.createdStartDate = null; if (StringUtils.isNotBlank(createdStartDate)) { @@ -158,10 +85,6 @@ public void setCreatedStartDate(String createdStartDate) throws ParseException { } } - public Date getCreatedEndDate() { - return this.createdEndDate; - } - public void setCreatedEndDate(String createdEndDate) throws ParseException { this.createdEndDate = null; if (StringUtils.isNotBlank(createdEndDate)) { @@ -169,10 +92,6 @@ public void setCreatedEndDate(String createdEndDate) throws ParseException { } } - public Date getModifiedStartDate() { - return this.modifiedStartDate; - } - public void setModifiedStartDate(String modifiedStartDate) throws ParseException { this.modifiedStartDate = null; if (StringUtils.isNotBlank(modifiedStartDate)) { @@ -180,14 +99,22 @@ public void setModifiedStartDate(String modifiedStartDate) throws ParseException } } - public Date getModifiedEndDate() { - return this.modifiedEndDate; - } - public void setModifiedEndDate(String modifiedEndDate) throws ParseException { this.modifiedEndDate = null; if (StringUtils.isNotBlank(modifiedEndDate)) { this.modifiedEndDate = TextUtils.parseDate(modifiedEndDate); } } + + public boolean isSimpleSearch() { + return this.searchMode == SearchMode.SIMPLE; + } + + public boolean isListAll() { + return this.query.isEmpty(); + } + + public boolean isSearchById() { + return StringUtils.isNumeric(query); + } } diff --git a/src/main/java/biblivre/circulation/user_cards/Handler.java b/src/main/java/biblivre/circulation/user_cards/Handler.java index 3245f490..ab866fbe 100644 --- a/src/main/java/biblivre/circulation/user_cards/Handler.java +++ b/src/main/java/biblivre/circulation/user_cards/Handler.java @@ -26,6 +26,7 @@ import biblivre.core.enums.ActionResult; import biblivre.core.file.DiskFile; import biblivre.labels.print.LabelPrintDTO; +import biblivre.search.SearchException; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -62,7 +63,12 @@ public void downloadPdf(ExtendedRequest request, ExtendedResponse response) { LabelPrintDTO dto = (LabelPrintDTO) request.getScopedSessionAttribute(printId); final DiskFile exportFile = userBO.printUserCardsToPDF(dto, request.getTranslationsMap()); - userBO.markAsPrinted(dto.getIds()); + try { + userBO.markAsPrinted(dto.getIds()); + } catch (SearchException e) { + this.setMessage(ActionResult.WARNING, "error.invalid_parameters"); + return; + } this.setFile(exportFile); diff --git a/src/main/java/biblivre/core/DTOCollection.java b/src/main/java/biblivre/core/DTOCollection.java index ea0ee9ab..d07b1a69 100644 --- a/src/main/java/biblivre/core/DTOCollection.java +++ b/src/main/java/biblivre/core/DTOCollection.java @@ -42,7 +42,7 @@ public void setPaging(PagingDTO paging) { this.paging = paging; } - public int getRecordLimit() { + public long getRecordLimit() { if (this.paging == null) { return 0; } diff --git a/src/main/java/biblivre/core/PagingDTO.java b/src/main/java/biblivre/core/PagingDTO.java index 577563fc..ef3bc238 100644 --- a/src/main/java/biblivre/core/PagingDTO.java +++ b/src/main/java/biblivre/core/PagingDTO.java @@ -20,15 +20,17 @@ package biblivre.core; import java.util.Date; +import lombok.Getter; +import lombok.Setter; import org.json.JSONObject; public final class PagingDTO implements IFJson { - private int recordCount; - private int recordOffset; - private int recordsPerPage; - private int recordLimit; + @Getter @Setter private long recordCount; + @Getter @Setter private long recordOffset; + @Setter @Getter private long recordsPerPage; + @Setter @Getter private long recordLimit; - private transient double time; + @Setter @Getter private transient double time; private transient long startTime; private transient long endTime; @@ -36,7 +38,7 @@ public PagingDTO() { this.startTimer(); } - public PagingDTO(int recordCount, int recordsPerPage, int recordOffset) { + public PagingDTO(long recordCount, int recordsPerPage, int recordOffset) { this.startTimer(); this.recordCount = recordCount; @@ -44,47 +46,7 @@ public PagingDTO(int recordCount, int recordsPerPage, int recordOffset) { this.recordOffset = recordOffset; } - public int getRecordCount() { - return this.recordCount; - } - - public void setRecordCount(int recordCount) { - this.recordCount = recordCount; - } - - public int getRecordsPerPage() { - return this.recordsPerPage; - } - - public void setRecordsPerPage(int recordsPerPage) { - this.recordsPerPage = recordsPerPage; - } - - public void setRecordsPerPage(String recordsPerPage) { - try { - this.recordsPerPage = Integer.parseInt(recordsPerPage); - } catch (Exception e) { - this.recordsPerPage = 20; - } - } - - public int getRecordOffset() { - return this.recordOffset; - } - - public void setRecordOffset(int recordOffset) { - this.recordOffset = recordOffset; - } - - public int getRecordLimit() { - return this.recordLimit; - } - - public void setRecordLimit(int recordLimit) { - this.recordLimit = recordLimit; - } - - public int getPage() { + public long getPage() { if (this.getRecordsPerPage() == 0) { return 1; } @@ -96,7 +58,7 @@ public void setPage(int page) { this.recordOffset = (page - 1) * this.getRecordsPerPage(); } - public int getPageCount() { + public long getPageCount() { if (this.getRecordCount() == 0) { return 0; } @@ -114,14 +76,6 @@ public int getPageCount() { return (int) Math.ceil(count / this.getRecordsPerPage()); } - public double getTime() { - return this.time; - } - - public void setTime(double time) { - this.time = time; - } - public void setTime(long start, long end) { this.time = (end - start) / 1000.0; } diff --git a/src/main/java/biblivre/search/ElasticsearchClientConfiguration.java b/src/main/java/biblivre/search/ElasticsearchClientConfiguration.java new file mode 100644 index 00000000..fb9a7349 --- /dev/null +++ b/src/main/java/biblivre/search/ElasticsearchClientConfiguration.java @@ -0,0 +1,31 @@ +package biblivre.search; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; +import org.springframework.data.elasticsearch.support.HttpHeaders; + +@Configuration +public class ElasticsearchClientConfiguration extends ElasticsearchConfiguration { + @Value("${elasticsearch.server.url}") + String serverUrl; + + @Value("${elasticsearch.api.key}") + String apiKey; + + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() + .connectedTo(serverUrl) + .usingSsl() + .withHeaders( + () -> { + var headers = new HttpHeaders(); + headers.add("Authorization", STR."ApiKey \{apiKey}"); + + return headers; + }) + .build(); + } +} diff --git a/src/main/java/biblivre/search/SearchException.java b/src/main/java/biblivre/search/SearchException.java new file mode 100644 index 00000000..4d89ed20 --- /dev/null +++ b/src/main/java/biblivre/search/SearchException.java @@ -0,0 +1,5 @@ +package biblivre.search; + +public class SearchException extends Exception { + public SearchException(String errorSearchingUsers, Throwable e) {} +} diff --git a/src/main/java/biblivre/search/user/IndexableUser.java b/src/main/java/biblivre/search/user/IndexableUser.java new file mode 100644 index 00000000..222df407 --- /dev/null +++ b/src/main/java/biblivre/search/user/IndexableUser.java @@ -0,0 +1,80 @@ +package biblivre.search.user; + +import java.util.Date; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.Setting; + +@Getter +@AllArgsConstructor +@Document(indexName = "users") +@Setting(settingPath = "/META-INF/elasticsearch/users.index.settings.json") +public class IndexableUser implements Persistable { + @Id private int id; + + @Field(type = FieldType.Text, analyzer = "name") + private String name; + + @Field(type = FieldType.Integer) + private int type; + + private String photoId; + + @Field(type = FieldType.Keyword) + private String status; + + @Field(type = FieldType.Integer) + private int loginId; + + @Field(type = FieldType.Date, pattern = "uuuuMMdd HHmmss.SSSXXX") + private Date created; + + @Field(type = FieldType.Integer) + private int createdBy; + + @Field(type = FieldType.Date, pattern = "uuuuMMdd HHmmss.SSSXXX") + private Date modified; + + @Field(type = FieldType.Integer) + private int modifiedBy; + + @Field(type = FieldType.Boolean) + private boolean userCardPrinted; + + @Field(type = FieldType.Flattened) + private Map fields; + + @Field(type = FieldType.Boolean) + private boolean hasPendingFines; + + @Field(type = FieldType.Boolean) + private boolean hasPendingLoans; + + @Field(type = FieldType.Boolean) + private boolean hasLogin; + + @Field(type = FieldType.Boolean) + private boolean isInactive; + + @Field(type = FieldType.Keyword) + private String schema; + + @Field(type = FieldType.Keyword) + private String tenant; + + @Override + public boolean isNew() { + return id > 0; + } + + @Override + public Integer getId() { + return id; + } +} diff --git a/src/main/java/biblivre/search/user/IndexableUserQueryParameters.java b/src/main/java/biblivre/search/user/IndexableUserQueryParameters.java new file mode 100644 index 00000000..8692c115 --- /dev/null +++ b/src/main/java/biblivre/search/user/IndexableUserQueryParameters.java @@ -0,0 +1,12 @@ +package biblivre.search.user; + +public record IndexableUserQueryParameters( + long id, + String name, + String schema, + String tenant, + boolean hasLogin, + boolean hasPendingFines, + boolean hasPendingLoans, + boolean hasNeverPrintedUserCard, + boolean isInactive) {} diff --git a/src/main/java/biblivre/search/user/IndexableUserRepository.java b/src/main/java/biblivre/search/user/IndexableUserRepository.java new file mode 100644 index 00000000..aad951c1 --- /dev/null +++ b/src/main/java/biblivre/search/user/IndexableUserRepository.java @@ -0,0 +1,7 @@ +package biblivre.search.user; + +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +public interface IndexableUserRepository extends ElasticsearchRepository { + void deleteBySchemaAndTenant(String schema, String tenant); +} diff --git a/src/main/resources/META-INF/elasticsearch/users.index.settings.json b/src/main/resources/META-INF/elasticsearch/users.index.settings.json new file mode 100644 index 00000000..6400b4ca --- /dev/null +++ b/src/main/resources/META-INF/elasticsearch/users.index.settings.json @@ -0,0 +1,14 @@ +{ + "analysis": { + "analyzer": { + "name": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "asciifolding" + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/circulation/user.jsp b/src/main/webapp/WEB-INF/jsp/circulation/user.jsp index 36f655ab..013f409c 100644 --- a/src/main/webapp/WEB-INF/jsp/circulation/user.jsp +++ b/src/main/webapp/WEB-INF/jsp/circulation/user.jsp @@ -44,7 +44,7 @@ $(document).ready(function() { var global = Globalize.culture().calendars.standard; - $('#search_box input.datepicker').Zebra_DatePicker({ + $('input.datepicker').Zebra_DatePicker({ days: global.days.names, days_abbr: global.days.namesAbbr, months: global.months.names, diff --git a/src/test/java/biblivre/administration/report/JasperReportBOTest.java b/src/test/java/biblivre/administration/report/JasperReportBOTest.java index 2e2e6d35..084aa0c2 100644 --- a/src/test/java/biblivre/administration/report/JasperReportBOTest.java +++ b/src/test/java/biblivre/administration/report/JasperReportBOTest.java @@ -14,6 +14,7 @@ import biblivre.core.translations.TranslationsMap; import biblivre.core.utils.Constants; import biblivre.core.utils.StringPool; +import biblivre.search.SearchException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,7 +35,7 @@ public class JasperReportBOTest extends AbstractContainerDatabaseTest { @Autowired private TranslationBO translationBO; @Test - public void testAllReportsInjected() { + public void testAllReportsInjected() throws SearchException { SchemaThreadLocal.setSchema(Constants.SINGLE_SCHEMA); UserDTO user = newReaderUser();