From a66fffb1bb3b475074c9a7e65277331cf8cb0a46 Mon Sep 17 00:00:00 2001 From: Alex Chernyshev Date: Tue, 31 Oct 2023 18:30:22 +0300 Subject: [PATCH 1/5] Create README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..18fcf0159d --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# lorsource +Linux.org.ru website engine + +Contains collection of my commits to LOR site engine From 6b4dbceaa604fae64a12b4eee7d40734c56511bf Mon Sep 17 00:00:00 2001 From: "Alex (aurora)" Date: Thu, 2 Nov 2023 16:32:04 +0300 Subject: [PATCH 2/5] "Jump to date" feature, initial implementation --- .../ru/org/linux/search/SearchRequest.java | 34 +++++++++++++++++-- .../ru/org/linux/search/SearchViewer.scala | 5 ++- src/main/webapp/WEB-INF/jsp/search.jsp | 32 ++++++++++++++--- src/main/webapp/WEB-INF/jsp/whois.jsp | 5 ++- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/org/linux/search/SearchRequest.java b/src/main/java/ru/org/linux/search/SearchRequest.java index 43bc779f9f..0ce55d7a6c 100644 --- a/src/main/java/ru/org/linux/search/SearchRequest.java +++ b/src/main/java/ru/org/linux/search/SearchRequest.java @@ -21,6 +21,8 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -35,7 +37,9 @@ public class SearchRequest { private SearchInterval interval = SearchInterval.ALL; private SearchRange range = SearchRange.ALL; private int offset = 0; - + private long dt; + public long getDt() { return dt; } + public void setDt(long dt) { this.dt = dt; } public String getQ() { return q; } @@ -45,7 +49,7 @@ public void setQ(String q) { } public boolean isInitial() { - return q.isEmpty() && user==null; + return q.isEmpty() && user==null && !isDateSelected(); } public boolean isUsertopic() { @@ -155,11 +159,35 @@ public String getQuery(int newOffset) { return buildParams(params); } + public boolean isDateSelected() { + return dt >0; + } + + public long atEndOfDaySelected() { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(dt)); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTime().getTime(); + } + + public long atStartOfDaySelected() { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(dt)); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime().getTime(); + } + private static String buildParams(Map params) { StringBuilder str = new StringBuilder(); for (Entry entry : params.entrySet()) { - if (str.length()>0) { + if (!str.isEmpty()) { str.append('&'); } diff --git a/src/main/scala/ru/org/linux/search/SearchViewer.scala b/src/main/scala/ru/org/linux/search/SearchViewer.scala index 596a6d0cd5..bcf60b9cd0 100644 --- a/src/main/scala/ru/org/linux/search/SearchViewer.scala +++ b/src/main/scala/ru/org/linux/search/SearchViewer.scala @@ -58,6 +58,9 @@ class SearchViewer(query: SearchRequest, elastic: ElasticClient) { val typeFilter = Option(query.getRange.getValue) map { value => termQuery(query.getRange.getColumn, value) } + val selectedDateFilter = Option(query) map { query => + rangeQuery(query.getInterval.getColumn) gte query.atStartOfDaySelected() lte query.atEndOfDaySelected() + } val dateFilter = Option(query.getInterval.getRange) map { range => rangeQuery(query.getInterval.getColumn) gt range @@ -71,7 +74,7 @@ class SearchViewer(query: SearchRequest, elastic: ElasticClient) { } } - val queryFilters = (typeFilter ++ dateFilter ++ userFilter).toSeq + val queryFilters = (typeFilter ++ (if (query.isDateSelected) selectedDateFilter else dateFilter) ++ userFilter).toSeq val esQuery = wrapQuery(boost(processQueryString(query.getQ)), queryFilters) diff --git a/src/main/webapp/WEB-INF/jsp/search.jsp b/src/main/webapp/WEB-INF/jsp/search.jsp index b6d7d479df..256b544e66 100644 --- a/src/main/webapp/WEB-INF/jsp/search.jsp +++ b/src/main/webapp/WEB-INF/jsp/search.jsp @@ -52,11 +52,22 @@ -
- -
+ + +
+ + + +
+
+ +
+ +
+
+
@@ -157,4 +168,15 @@ + + diff --git a/src/main/webapp/WEB-INF/jsp/whois.jsp b/src/main/webapp/WEB-INF/jsp/whois.jsp index 24453619ba..e620e8eacd 100644 --- a/src/main/webapp/WEB-INF/jsp/whois.jsp +++ b/src/main/webapp/WEB-INF/jsp/whois.jsp @@ -79,7 +79,10 @@ subDomainTitleFormat: { empty: "{date}", filled: "{date}
сообщений: {count}" - } + }, + onClick: function (date, nb) { + window.open('../search.jsp?dt='+date.getTime()+'&user=${user.nick}', '_blank'); + } }); } }); From 639131a6a31fc0dbccbe5a6129f91b06721d7d22 Mon Sep 17 00:00:00 2001 From: "Alex (aurora)" Date: Thu, 2 Nov 2023 16:52:28 +0300 Subject: [PATCH 3/5] "Reaction on me" feature, hide deleted comments/topics for ordinary users but keep visible for moderators. --- .../scala/ru/org/linux/reaction/ReactionDao.scala | 14 +++++++++----- .../ru/org/linux/reaction/ReactionService.scala | 4 ++-- .../linux/reaction/UserReactionsController.scala | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/scala/ru/org/linux/reaction/ReactionDao.scala b/src/main/scala/ru/org/linux/reaction/ReactionDao.scala index 1831e4d852..a367572bce 100644 --- a/src/main/scala/ru/org/linux/reaction/ReactionDao.scala +++ b/src/main/scala/ru/org/linux/reaction/ReactionDao.scala @@ -130,22 +130,25 @@ class ReactionDao(ds: DataSource, val transactionManager: PlatformTransactionMan reaction = rs.getString("reaction")) } - def getReactionsView(originUser: User, offset: Int, size: Int,isReactionsOn: Boolean): Seq[ReactionsView] = + def getReactionsView(originUser: User, offset: Int, size: Int,isReactionsOn: Boolean,isIncludeDeleted: Boolean): Seq[ReactionsView] = jdbcTemplate.queryAndMap( if (isReactionsOn) "WITH constants (selectedId) as ( values (?) ) " + " select r.topic_id,r.comment_id,r.set_date, r.reaction,r.origin_user as \"target_user\", g.\"section\", g.urlname, t.title " + " from reactions_log r " + - " join topics t ON r.topic_id = t.id AND NOT t.deleted " + + " join topics t ON r.topic_id = t.id " + + (if (!isIncludeDeleted) { " AND NOT t.deleted" } else "" ) + " join groups g ON t.groupid = g.id " + " WHERE r.comment_id is null and t.userid=(select selectedId from constants) " + " UNION ALL " + " select r.topic_id,r.comment_id,r.set_date, r.reaction, r.origin_user, g.\"section\", g.urlname, t.title " + " from reactions_log r " + - " join topics t ON r.topic_id = t.id AND NOT t.deleted " + + " join topics t ON r.topic_id = t.id " + + (if (!isIncludeDeleted) { " AND NOT t.deleted" } else "") + " JOIN comments c ON c.id = r.comment_id " + " join groups g ON t.groupid = g.id " + " WHERE c.userid=(select selectedId from constants) " + + (if (!isIncludeDeleted) { " AND NOT c.deleted" } else "") + " order by set_date desc OFFSET ? LIMIT ?" else "SELECT topic_id, comment_id, set_date, reaction, topics.title, " + @@ -153,8 +156,9 @@ class ReactionDao(ds: DataSource, val transactionManager: PlatformTransactionMan "FROM reactions_log JOIN topics ON topic_id = topics.id " + "JOIN groups ON topics.groupid = groups.id " + "LEFT JOIN comments ON comment_id = comments.id " + - "WHERE origin_user=? AND NOT topics.deleted AND comments.deleted IS NOT TRUE " + - "ORDER BY set_date DESC OFFSET ? LIMIT ?", + "WHERE origin_user=? " + + (if (!isIncludeDeleted) { " AND NOT topics.deleted AND comments.deleted IS NOT TRUE " } else "") + + " ORDER BY set_date DESC OFFSET ? LIMIT ?", originUser.getId, offset, size) { case (rs, _) => ReactionsView( item = ReactionsLogItem( diff --git a/src/main/scala/ru/org/linux/reaction/ReactionService.scala b/src/main/scala/ru/org/linux/reaction/ReactionService.scala index f5f3c30b04..1a44f1f3cb 100644 --- a/src/main/scala/ru/org/linux/reaction/ReactionService.scala +++ b/src/main/scala/ru/org/linux/reaction/ReactionService.scala @@ -209,8 +209,8 @@ class ReactionService(userService: UserService, reactionDao: ReactionDao, topicD r } - def getReactionsView(originUser: User, offset: Int, size: Int,modeTo: Boolean): Seq[PreparedReactionView] = { - val items = reactionDao.getReactionsView(originUser, offset, size,modeTo) + def getReactionsView(originUser: User, offset: Int, size: Int,modeTo: Boolean,allowDeleted:Boolean): Seq[PreparedReactionView] = { + val items = reactionDao.getReactionsView(originUser, offset, size,modeTo,allowDeleted) val textIds = items.view.map(_.item).map(i => i.commentId.getOrElse(i.topicId)).distinct.toSeq val targetUserIds = items.view.map(_.targetUserId).distinct.toSeq diff --git a/src/main/scala/ru/org/linux/reaction/UserReactionsController.scala b/src/main/scala/ru/org/linux/reaction/UserReactionsController.scala index 90a0a2900a..ee19393ca7 100644 --- a/src/main/scala/ru/org/linux/reaction/UserReactionsController.scala +++ b/src/main/scala/ru/org/linux/reaction/UserReactionsController.scala @@ -60,19 +60,21 @@ class UserReactionsController(reactionService: ReactionService, userService: Use modelAndView.addObject("url", url) modelAndView.addObject("reactionsUrl", s"/people/${user.getNick}/reactions/to") + val showDeleted = currentUser.moderator + val items =if (mode != null) { if ("to".equalsIgnoreCase(mode)) { // признак включения режима "реакции на меня" modelAndView.addObject("modeTo", 1) // переделка url для пагинации url = s"/people/${user.getNick}/reactions/to" - reactionService.getReactionsView(user, offset, ItemsPerPage + 1, modeTo = true) + reactionService.getReactionsView(user, offset, ItemsPerPage + 1, modeTo = true,showDeleted) } else throw new BadParameterException("mode", "incorrect") } else { // вариант по-умолчанию (мои реакции) - reactionService.getReactionsView(user, offset, ItemsPerPage + 1, modeTo = false) + reactionService.getReactionsView(user, offset, ItemsPerPage + 1, modeTo = false,showDeleted) } modelAndView.addObject("items", items.take(ItemsPerPage).asJava) From fa5bc3eafbe294751a4925fca12e0e16cc1c7439 Mon Sep 17 00:00:00 2001 From: "Alex (aurora)" Date: Thu, 2 Nov 2023 17:37:32 +0300 Subject: [PATCH 4/5] remove README --- README.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 18fcf0159d..0000000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# lorsource -Linux.org.ru website engine - -Contains collection of my commits to LOR site engine From d78798de1e0084c3616ed3f44defca78f16c8338 Mon Sep 17 00:00:00 2001 From: "Alex (aurora)" Date: Fri, 3 Nov 2023 11:39:41 +0300 Subject: [PATCH 5/5] Second version, after review. --- .../java/ru/org/linux/search/SearchController.java | 8 ++++++-- .../java/ru/org/linux/search/SearchRequest.java | 13 ++++++++++--- .../scala/ru/org/linux/reaction/ReactionDao.scala | 10 +++++----- .../scala/ru/org/linux/search/SearchViewer.scala | 9 ++++----- src/main/webapp/WEB-INF/jsp/whois.jsp | 2 +- .../search/SearchResultServiceIntegrationSpec.scala | 2 +- .../linux/search/SearchViewerIntegrationSpec.scala | 2 +- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/org/linux/search/SearchController.java b/src/main/java/ru/org/linux/search/SearchController.java index 1c742bfb9b..6698dc22d5 100644 --- a/src/main/java/ru/org/linux/search/SearchController.java +++ b/src/main/java/ru/org/linux/search/SearchController.java @@ -24,6 +24,7 @@ import com.sksamuel.elastic4s.requests.searches.aggs.responses.FilterAggregationResult; import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.TermBucket; import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.Terms; +import org.joda.time.DateTimeZone; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -52,6 +53,7 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; +import javax.servlet.http.HttpServletRequest; @Controller public class SearchController { @@ -107,7 +109,8 @@ public static Map getRanges() { public String search( Model model, @ModelAttribute("query") SearchRequest query, - BindingResult bindingResult + BindingResult bindingResult, + HttpServletRequest request ) { Map params = model.asMap(); @@ -116,7 +119,8 @@ public String search( SearchViewer sv = new SearchViewer(query, client); - SearchResponse response = sv.performSearch(); + final DateTimeZone tz = (DateTimeZone)request.getAttribute("timezone"); + SearchResponse response = sv.performSearch(tz); long current = System.currentTimeMillis(); diff --git a/src/main/java/ru/org/linux/search/SearchRequest.java b/src/main/java/ru/org/linux/search/SearchRequest.java index 0ce55d7a6c..849a4e5669 100644 --- a/src/main/java/ru/org/linux/search/SearchRequest.java +++ b/src/main/java/ru/org/linux/search/SearchRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 1998-2016 Linux.org.ru + * Copyright 1998-2023 Linux.org.ru * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,6 +15,7 @@ package ru.org.linux.search; +import org.joda.time.DateTimeZone; import ru.org.linux.search.SearchEnums.SearchInterval; import ru.org.linux.search.SearchEnums.SearchRange; import ru.org.linux.user.User; @@ -163,9 +164,12 @@ public boolean isDateSelected() { return dt >0; } - public long atEndOfDaySelected() { + public long atEndOfDaySelected(DateTimeZone tz) { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date(dt)); + if (tz!=null) { + calendar.setTimeZone(tz.toTimeZone()); + } calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); @@ -173,9 +177,12 @@ public long atEndOfDaySelected() { return calendar.getTime().getTime(); } - public long atStartOfDaySelected() { + public long atStartOfDaySelected(DateTimeZone tz) { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date(dt)); + if (tz!=null) { + calendar.setTimeZone(tz.toTimeZone()); + } calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); diff --git a/src/main/scala/ru/org/linux/reaction/ReactionDao.scala b/src/main/scala/ru/org/linux/reaction/ReactionDao.scala index de679104db..7d0334d1be 100644 --- a/src/main/scala/ru/org/linux/reaction/ReactionDao.scala +++ b/src/main/scala/ru/org/linux/reaction/ReactionDao.scala @@ -129,25 +129,25 @@ class ReactionDao(ds: DataSource, val transactionManager: PlatformTransactionMan reaction = rs.getString("reaction")) } - def getReactionsView(originUser: User, offset: Int, size: Int,isReactionsOn: Boolean,isIncludeDeleted: Boolean): Seq[ReactionsView] = + def getReactionsView(originUser: User, offset: Int, size: Int,isReactionsOn: Boolean,includeDeleted: Boolean): Seq[ReactionsView] = jdbcTemplate.queryAndMap( if (isReactionsOn) "WITH constants (selectedId) as ( values (?) ) " + " select r.topic_id,r.comment_id,r.set_date, r.reaction,r.origin_user as \"target_user\", g.\"section\", g.urlname, t.title " + " from reactions_log r " + " join topics t ON r.topic_id = t.id " + - (if (!isIncludeDeleted) { " AND NOT t.deleted" } else "" ) + + (if (!includeDeleted) { " AND NOT t.deleted" } else "" ) + " join groups g ON t.groupid = g.id " + " WHERE r.comment_id is null and t.userid=(select selectedId from constants) " + " UNION ALL " + " select r.topic_id,r.comment_id,r.set_date, r.reaction, r.origin_user, g.\"section\", g.urlname, t.title " + " from reactions_log r " + " join topics t ON r.topic_id = t.id " + - (if (!isIncludeDeleted) { " AND NOT t.deleted" } else "") + + (if (!includeDeleted) { " AND NOT t.deleted" } else "") + " JOIN comments c ON c.id = r.comment_id " + " join groups g ON t.groupid = g.id " + " WHERE c.userid=(select selectedId from constants) " + - (if (!isIncludeDeleted) { " AND NOT c.deleted" } else "") + + (if (!includeDeleted) { " AND NOT c.deleted" } else "") + " order by set_date desc OFFSET ? LIMIT ?" else "SELECT topic_id, comment_id, set_date, reaction, topics.title, " + @@ -156,7 +156,7 @@ class ReactionDao(ds: DataSource, val transactionManager: PlatformTransactionMan "JOIN groups ON topics.groupid = groups.id " + "LEFT JOIN comments ON comment_id = comments.id " + "WHERE origin_user=? " + - (if (!isIncludeDeleted) { " AND NOT topics.deleted AND comments.deleted IS NOT TRUE " } else "") + + (if (!includeDeleted) { " AND NOT topics.deleted AND comments.deleted IS NOT TRUE " } else "") + " ORDER BY set_date DESC OFFSET ? LIMIT ?", originUser.getId, offset, size) { case (rs, _) => ReactionsView( diff --git a/src/main/scala/ru/org/linux/search/SearchViewer.scala b/src/main/scala/ru/org/linux/search/SearchViewer.scala index bcf60b9cd0..9e2c9eb9ff 100644 --- a/src/main/scala/ru/org/linux/search/SearchViewer.scala +++ b/src/main/scala/ru/org/linux/search/SearchViewer.scala @@ -20,6 +20,7 @@ import com.sksamuel.elastic4s.ElasticDsl.* import com.sksamuel.elastic4s.requests.searches.SearchResponse import com.sksamuel.elastic4s.requests.searches.queries.Query import com.sksamuel.elastic4s.requests.searches.queries.funcscorer.WeightScore +import org.joda.time.DateTimeZone import scala.concurrent.Await import scala.concurrent.duration.* @@ -54,13 +55,11 @@ class SearchViewer(query: SearchRequest, elastic: ElasticClient) { } } - def performSearch: SearchResponse = { + def performSearch(tz:DateTimeZone): SearchResponse = { val typeFilter = Option(query.getRange.getValue) map { value => termQuery(query.getRange.getColumn, value) } - val selectedDateFilter = Option(query) map { query => - rangeQuery(query.getInterval.getColumn) gte query.atStartOfDaySelected() lte query.atEndOfDaySelected() - } + val selectedDateFilter = rangeQuery(query.getInterval.getColumn) gte query.atStartOfDaySelected(tz) lte query.atEndOfDaySelected(tz) val dateFilter = Option(query.getInterval.getRange) map { range => rangeQuery(query.getInterval.getColumn) gt range @@ -74,7 +73,7 @@ class SearchViewer(query: SearchRequest, elastic: ElasticClient) { } } - val queryFilters = (typeFilter ++ (if (query.isDateSelected) selectedDateFilter else dateFilter) ++ userFilter).toSeq + val queryFilters = (typeFilter ++ (if (query.isDateSelected) Option(selectedDateFilter) else dateFilter) ++ userFilter).toSeq val esQuery = wrapQuery(boost(processQueryString(query.getQ)), queryFilters) diff --git a/src/main/webapp/WEB-INF/jsp/whois.jsp b/src/main/webapp/WEB-INF/jsp/whois.jsp index e620e8eacd..71be00bba3 100644 --- a/src/main/webapp/WEB-INF/jsp/whois.jsp +++ b/src/main/webapp/WEB-INF/jsp/whois.jsp @@ -81,7 +81,7 @@ filled: "{date}
сообщений: {count}" }, onClick: function (date, nb) { - window.open('../search.jsp?dt='+date.getTime()+'&user=${user.nick}', '_blank'); + window.open('/search.jsp?dt='+date.getTime()+'&user=${user.nick}', '_blank'); } }); } diff --git a/src/test/scala/ru/org/linux/search/SearchResultServiceIntegrationSpec.scala b/src/test/scala/ru/org/linux/search/SearchResultServiceIntegrationSpec.scala index ffe2db6618..b136150950 100644 --- a/src/test/scala/ru/org/linux/search/SearchResultServiceIntegrationSpec.scala +++ b/src/test/scala/ru/org/linux/search/SearchResultServiceIntegrationSpec.scala @@ -54,7 +54,7 @@ class SearchResultServiceIntegrationSpec extends SpecificationWithJUnit { "SearchResultsService" should { "prepare some results" in new IndexFixture { - val response = new SearchViewer(new SearchRequest(), elastic).performSearch + val response = new SearchViewer(new SearchRequest(), elastic).performSearch(null) val prepared = response.hits.hits.map(service.prepare) diff --git a/src/test/scala/ru/org/linux/search/SearchViewerIntegrationSpec.scala b/src/test/scala/ru/org/linux/search/SearchViewerIntegrationSpec.scala index 3884f5e451..7129ab0f29 100644 --- a/src/test/scala/ru/org/linux/search/SearchViewerIntegrationSpec.scala +++ b/src/test/scala/ru/org/linux/search/SearchViewerIntegrationSpec.scala @@ -45,7 +45,7 @@ class SearchViewerIntegrationSpec extends SpecificationWithJUnit { "SearchViewer" should { "make valid default search" in new IndexFixture { - val response = new SearchViewer(new SearchRequest(), elastic).performSearch + val response = new SearchViewer(new SearchRequest(), elastic).performSearch(null) response.totalHits must be equalTo 0 }