diff options
6 files changed, 101 insertions, 25 deletions
diff --git a/juick-core/src/main/java/com/juick/Message.java b/juick-core/src/main/java/com/juick/Message.java index 4a693558..0d91cc63 100644 --- a/juick-core/src/main/java/com/juick/Message.java +++ b/juick-core/src/main/java/com/juick/Message.java @@ -42,6 +42,7 @@ public class Message implements Comparable { private User user = null; private final List<Tag> tags; private Instant ts; + private Instant updated; @XmlTransient @JsonIgnore public int TimeAgo = 0; @@ -339,4 +340,12 @@ public class Message implements Comparable { public void setAttachment(Attachment attachment) { this.attachment = attachment; } + + public Instant getUpdated() { + return updated; + } + + public void setUpdated(Instant updated) { + this.updated = updated; + } } diff --git a/juick-server-core/src/main/java/com/juick/service/MessagesService.java b/juick-server-core/src/main/java/com/juick/service/MessagesService.java index ee679ca4..b90cca79 100644 --- a/juick-server-core/src/main/java/com/juick/service/MessagesService.java +++ b/juick-server-core/src/main/java/com/juick/service/MessagesService.java @@ -64,7 +64,7 @@ public interface MessagesService { List<Integer> getPrivate(int uid, int before); - List<Integer> getDiscussions(int uid, int before); + List<Integer> getDiscussions(int uid, Long to); List<Integer> getRecommended(int uid, int before); @@ -88,7 +88,7 @@ public interface MessagesService { List<Integer> getUserSearch(int UID, String search, int privacy, int before); - List<com.juick.Message> getMessages(Collection<Integer> mids); + List<com.juick.Message> getMessages(List<Integer> mids); List<com.juick.Message> getReplies(int mid); diff --git a/juick-server-jdbc/src/main/java/com/juick/service/MessagesServiceImpl.java b/juick-server-jdbc/src/main/java/com/juick/service/MessagesServiceImpl.java index de8b256e..d7067901 100644 --- a/juick-server-jdbc/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/juick-server-jdbc/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -97,6 +97,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ msg.setRepliesBy(rs.getString(19)); msg.setText(rs.getString(20)); msg.setReplyQuote(MessageUtils.formatQuote(rs.getString(21))); + msg.setUpdated(rs.getTimestamp(22).toInstant()); try { imagesService.setAttachmentMetadata(imgDir, baseImagesUrl, msg); } catch (Exception e) { @@ -305,7 +306,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ + "messages.readonly, messages.privacy, messages.replies," + "messages.attach, messages.place_id, messages.lat," + "messages.lon, COUNT(favorites.user_id) as likes, messages.hidden," - + "txt.tags, txt.repliesby, txt.txt, '' as q FROM messages " + + "txt.tags, txt.repliesby, txt.txt, '' as q, messages.updated FROM messages " + "INNER JOIN users ON messages.user_id = users.id " + "INNER JOIN messages_txt AS txt " + "ON messages.message_id = txt.message_id " @@ -511,18 +512,20 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Transactional(readOnly = true) @Override - public List<Integer> getDiscussions(final int uid, final int before) { + public List<Integer> getDiscussions(final int uid, final Long to) { SqlParameterSource sqlParameterSource = new MapSqlParameterSource() .addValue("uid", uid) - .addValue("before", before); + .addValue("to", new Timestamp(to)); - return getNamedParameterJdbcTemplate().queryForList( - "SELECT message_id FROM subscr_messages WHERE suser_id = :uid" + - (before > 0 ? - " AND message_id < :before " : " ") + - "ORDER BY message_id DESC LIMIT 20", + return getNamedParameterJdbcTemplate().query( + "SELECT messages.message_id, messages.updated FROM subscr_messages " + + "INNER JOIN messages ON messages.message_id=subscr_messages.message_id " + + "WHERE suser_id = :uid " + + (to != 0 ? + "AND updated < :to " : StringUtils.EMPTY) + + "ORDER BY updated DESC, message_id DESC LIMIT 20", sqlParameterSource, - Integer.class); + (rs, rowNum) -> rs.getInt(1)); } @Transactional(readOnly = true) @@ -722,9 +725,9 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Transactional(readOnly = true) @Override - public List<com.juick.Message> getMessages(final Collection<Integer> mids) { + public List<com.juick.Message> getMessages(final List<Integer> mids) { if (CollectionUtils.isNotEmpty(mids)) { - return getNamedParameterJdbcTemplate().query( + List<com.juick.Message> msgs = getNamedParameterJdbcTemplate().query( "SELECT messages.message_id, 0 as rid, 0 as replyto, " + "messages.user_id,users.nick, 0 as banned, " + "TIMESTAMPDIFF(MINUTE,messages.ts,NOW())," @@ -732,15 +735,18 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ + "messages.readonly,messages.privacy,messages.replies," + "messages.attach,messages.place_id,messages.lat," + "messages.lon,COUNT(favorites.user_id) AS likes,messages.hidden," - + "messages_txt.tags,messages_txt.repliesby, messages_txt.txt, '' as q " + + "messages_txt.tags,messages_txt.repliesby, messages_txt.txt, '' as q, " + + "messages.updated " + "FROM (messages INNER JOIN messages_txt " + "ON messages.message_id=messages_txt.message_id) " + "INNER JOIN users ON messages.user_id=users.id " + "LEFT JOIN favorites " + "ON messages.message_id = favorites.message_id " - + "WHERE messages.message_id IN (:ids) GROUP BY messages.message_id ORDER BY messages.message_id DESC", + + "WHERE messages.message_id IN (:ids) GROUP BY messages.message_id", new MapSqlParameterSource("ids", mids), new MessageMapper()); + msgs.sort(Comparator.comparing(item -> mids.indexOf(item.getMid()))); + return msgs; } return Collections.emptyList(); } @@ -756,7 +762,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "replies.attach, 0 as place_id, 0 as lat, " + "0 as lon, 0 as likes, 0 as hidden, " + "NULL as tags, NULL as repliesby, replies.txt, " + - "IFNULL(qw.txt, t.txt) as q " + + "IFNULL(qw.txt, t.txt) as q, " + + "NOW() " + "FROM replies INNER JOIN users " + "ON replies.user_id = users.id " + "LEFT JOIN replies qw ON replies.message_id = qw.message_id and replies.replyto = qw.reply_id " + @@ -880,10 +887,11 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Transactional(readOnly = true) @Override public List<com.juick.Message> getNotifications(User user, LocalDateTime before) { - return getNamedParameterJdbcTemplate().query("SELECT n.message_id as mid, n.reply_id, n.replyto, " + + return getNamedParameterJdbcTemplate().query("SELECT n.message_id as mid, n.reply_id, " + + "n.replyto, " + "n.user_id, users.nick, users.banned, 0 as ago, n.ts, 0 as readonly, 0 as privacy, " + "0 as replies, n.attach, 0 as place_id, 0 as lat, 0 as lon, 0 as likes, 0 as hidden, " + - "NULL as tags, NULL as repliesby, n.txt, IFNULL(qw.txt, t.txt) as q " + + "NULL as tags, NULL as repliesby, n.txt, IFNULL(qw.txt, t.txt) as q, NOW() " + "FROM (SELECT * FROM replies WHERE EXISTS (SELECT 1 FROM subscr_messages WHERE suser_id=:uid " + "AND replies.user_id!=:uid AND replies.message_id=message_id " + (before != null ? "AND replies.ts < :before " : StringUtils.EMPTY) + diff --git a/juick-server-jdbc/src/test/java/com/juick/service/MessageServiceTest.java b/juick-server-jdbc/src/test/java/com/juick/service/MessageServiceTest.java index bdb9cdcb..4ebfe056 100644 --- a/juick-server-jdbc/src/test/java/com/juick/service/MessageServiceTest.java +++ b/juick-server-jdbc/src/test/java/com/juick/service/MessageServiceTest.java @@ -35,6 +35,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.inject.Inject; import java.sql.Timestamp; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -164,6 +166,8 @@ public class MessageServiceTest extends AbstractJUnit4SpringContextTests { assertEquals(-1, userService.checkPassword(ugnich.getName(), "xy")); subscriptionService.subscribeMessage(msg.getMid(), ugnich.getUid()); assertEquals(1, subscriptionService.getUsersSubscribedToComments(msg.getMid(), user.getUid()).size()); + assertThat(messagesService.getDiscussions(ugnich.getUid(), Instant.now().toEpochMilli()).get(0), + equalTo(msg.getMid())); messagesService.deleteMessage(user_id, mid); messagesService.deleteMessage(user_id, mid2); String htmlTagName = ">_<"; diff --git a/juick-www/src/main/java/com/juick/www/controllers/Messages.java b/juick-www/src/main/java/com/juick/www/controllers/Messages.java index e74f7486..c83bb356 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/Messages.java +++ b/juick-www/src/main/java/com/juick/www/controllers/Messages.java @@ -41,6 +41,9 @@ import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URLEncoder; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.function.BooleanSupplier; @@ -117,6 +120,7 @@ public class Messages { @RequestParam(name = "show", required = false) String paramShow, @RequestParam(name = "search", required = false) String paramSearch, @RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore, + @RequestParam(name = "to", required = false, defaultValue = "0") Long paramTo, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, ModelMap model) throws IOException { if (tag != null) { @@ -156,7 +160,7 @@ public class Messages { mids = messagesService.getPrivate(visitor.getUid(), paramBefore); } else if (paramShow.equals("discuss") && !visitor.isAnonymous()) { title = "Обсуждения"; - mids = messagesService.getDiscussions(visitor.getUid(), paramBefore); + mids = messagesService.getDiscussions(visitor.getUid(), paramTo); } else if (paramShow.equals("recommended") && !visitor.isAnonymous()) { title = "Рекомендации"; mids = messagesService.getRecommended(visitor.getUid(), paramBefore); @@ -194,7 +198,7 @@ public class Messages { model.addAttribute("showAdv", paramShow == null && paramBefore == 0 && paramSearch == null && visitor.getUid() == 0); if (mids.size() >= 20) { - String nextpage = "?before=" + mids.get(mids.size() - 1); + String nextpage = (paramShow != null && paramShow.equals("discuss")) ? "?to=" + msgs.get(msgs.size() - 1).getUpdated().toEpochMilli() : "?before=" + mids.get(mids.size() - 1); if (paramShow != null) { nextpage += "&show=" + paramShow; } diff --git a/juick-www/src/test/java/com/juick/www/WebAppTests.java b/juick-www/src/test/java/com/juick/www/WebAppTests.java index af6b3f89..9d822d77 100644 --- a/juick-www/src/test/java/com/juick/www/WebAppTests.java +++ b/juick-www/src/test/java/com/juick/www/WebAppTests.java @@ -28,11 +28,7 @@ import com.juick.Tag; import com.juick.User; import com.juick.configuration.DataConfiguration; import com.juick.configuration.RepositoryConfiguration; -import com.juick.service.ImagesService; -import com.juick.service.MessagesService; -import com.juick.service.MockImagesService; -import com.juick.service.PrivacyQueriesService; -import com.juick.service.UserService; +import com.juick.service.*; import com.juick.util.MessageUtils; import com.juick.www.configuration.SapeConfiguration; import com.juick.www.configuration.WebSecurityConfig; @@ -42,6 +38,7 @@ import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.template.PebbleTemplate; import org.apache.commons.text.StringEscapeUtils; +import org.eclipse.jetty.websocket.common.message.MessageAppender; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -72,6 +69,8 @@ import java.io.Writer; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; @@ -119,6 +118,8 @@ public class WebAppTests { private PrivacyQueriesService privacyQueriesService; @Inject private JdbcTemplate jdbcTemplate; + @Inject + private SubscriptionService subscriptionService; @Inject private PebbleEngine pebbleEngine; @@ -333,4 +334,54 @@ public class WebAppTests { public void nonExistentBlogShouldReturn404() throws Exception { mockMvc.perform(get("/ololoe/")).andExpect(status().isNotFound()); } + @Test + public void discussionsShouldBePageableByTimestamp() throws Exception { + String msgText = "Привет, я снова Угнич"; + int mid = messagesService.createMessage(ugnich.getUid(), msgText, null, null); + int midNew = messagesService.createMessage(ugnich.getUid(), "Я более новый Угнич", null, null); + MvcResult loginResult = mockMvc.perform(post("/login") + .param("username", freefdName) + .param("password", freefdPassword)).andReturn(); + Cookie loginCookie = loginResult.getResponse().getCookie("juick-remember-me"); + webClient.setCookieManager(new CookieManager()); + webClient.getCookieManager().addCookie( + new com.gargoylesoftware.htmlunit.util.Cookie(loginCookie.getDomain(), + loginCookie.getName(), + loginCookie.getValue())); + String discussionsUrl = "http://localhost:8080/?show=discuss"; + HtmlPage discussions = webClient.getPage(discussionsUrl); + assertThat(discussions.querySelectorAll("article").size(), is(0)); + subscriptionService.subscribeMessage(mid, freefd.getUid()); + discussions = (HtmlPage) discussions.refresh(); + assertThat(discussions.querySelectorAll("article").size(), is(1)); + subscriptionService.subscribeMessage(midNew, freefd.getUid()); + discussions = (HtmlPage) discussions.refresh(); + assertThat(discussions.querySelectorAll("article").size(), is(2)); + assertThat(discussions.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(midNew))); + messagesService.createReply(mid, 0, freefd.getUid(), "I'm replied", null); + discussions = (HtmlPage) discussions.refresh(); + assertThat(discussions.querySelectorAll("article").size(), is(2)); + assertThat(discussions.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(mid))); + Message msg = messagesService.getMessage(mid); + HtmlPage discussionsOld = webClient.getPage(discussionsUrl + "&to=" + msg.getUpdated().toEpochMilli()); + assertThat(discussionsOld.querySelectorAll("article").size(), is(1)); + assertThat(discussionsOld.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(midNew))); + List<Integer> newMids = IntStream.rangeClosed(1, 19).map(i -> messagesService.createMessage(ugnich.getUid(), String.valueOf(i), null, null)).boxed().collect(Collectors.toList()); + for (Integer m : newMids) { + subscriptionService.subscribeMessage(m, freefd.getUid()); + } + discussions = (HtmlPage) discussions.refresh(); + assertThat(discussions.querySelectorAll("article").size(), is(20)); + assertThat(discussions.querySelectorAll("article") + .get(19).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(mid))); + messagesService.createReply(midNew, 0, freefd.getUid(), "I'm replied", null); + discussions = (HtmlPage) discussions.refresh(); + assertThat(discussions.querySelectorAll("article") + .get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(midNew))); + Message old = messagesService.getMessage(newMids.get(0)); + discussionsOld = webClient.getPage(discussionsUrl + "&to=" + old.getUpdated().toEpochMilli()); + assertThat(discussionsOld.querySelectorAll("article").size(), is(1)); + assertThat(discussionsOld.querySelectorAll("article") + .get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(mid))); + } } |