From ad99117a15062a3819dad1f52a072e9694a954df Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 11 Apr 2018 21:34:14 +0300 Subject: server: last read marker for threads --- .../java/com/juick/server/CommandsManager.java | 4 ++-- .../java/com/juick/service/MessagesService.java | 6 ++++-- .../com/juick/service/MessagesServiceImpl.java | 21 ++++++++++++++----- .../com/juick/service/SubscriptionServiceImpl.java | 20 +++++------------- juick-server-jdbc/src/main/resources/schema.sql | 1 + .../main/java/com/juick/server/api/Messages.java | 2 +- .../java/com/juick/server/tests/ServerTests.java | 24 ++++++++++------------ .../com/juick/www/controllers/MessagesWWW.java | 2 +- .../java/com/juick/www/controllers/NewMessage.java | 2 +- juick-www/src/test/java/com/juick/WebAppTests.java | 13 ++++++------ 10 files changed, 49 insertions(+), 46 deletions(-) diff --git a/juick-common/src/main/java/com/juick/server/CommandsManager.java b/juick-common/src/main/java/com/juick/server/CommandsManager.java index d924ac70..734d68fb 100644 --- a/juick-common/src/main/java/com/juick/server/CommandsManager.java +++ b/juick-common/src/main/java/com/juick/server/CommandsManager.java @@ -388,7 +388,7 @@ public class CommandsManager { com.juick.Message msg = messagesService.getMessage(mid); if (msg != null) { if (showReplies) { - List replies = messagesService.getReplies(mid); + List replies = messagesService.getReplies(user, mid); replies.add(0, msg); return CommandResult.fromString(String.join("\n", replies.stream().map(PlainTextFormatter::formatPostSummary).collect(Collectors.toList()))); @@ -477,7 +477,7 @@ public class CommandsManager { } else { String attachmentStr = attachment.toString(); String attachmentType = StringUtils.isNotEmpty(attachmentStr) ? attachmentStr.substring(attachmentStr.length() - 3) : null; - int newrid = messagesService.createReply(mid, rid, user.getUid(), txt, attachmentType); + int newrid = messagesService.createReply(mid, rid, user, txt, attachmentType); if (StringUtils.isNotEmpty(attachmentType)) { String attachmentFName = attachment.getScheme().equals("juick") ? attachment.getHost() : HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); diff --git a/juick-common/src/main/java/com/juick/service/MessagesService.java b/juick-common/src/main/java/com/juick/service/MessagesService.java index 2dce2806..968e64be 100644 --- a/juick-common/src/main/java/com/juick/service/MessagesService.java +++ b/juick-common/src/main/java/com/juick/service/MessagesService.java @@ -29,7 +29,7 @@ import java.util.List; public interface MessagesService { int createMessage(int uid, String txt, String attachment, Collection tags); - int createReply(int mid, int rid, int uid, String txt, String attachment); + int createReply(int mid, int rid, User user, String txt, String attachment); int getReplyIDIncrement(int mid); @@ -95,7 +95,7 @@ public interface MessagesService { List getMessages(List mids); - List getReplies(int mid); + List getReplies(User user, int mid); boolean setMessagePopular(int mid, int popular); @@ -110,4 +110,6 @@ public interface MessagesService { List getLastReplies(int hours); List getPopularCandidates(); + + void setLastReadComment(User user, Integer mid, Integer rid); } 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 3b73ed25..c811e300 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 @@ -167,7 +167,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ /** * @param mid * @param rid - * @param uid + * @param user * @param txt * @param attachment * @return @@ -175,17 +175,18 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ */ @Transactional @Override - public int createReply(final int mid, final int rid, final int uid, final String txt, final String attachment) { + public int createReply(final int mid, final int rid, final User user, final String txt, final String attachment) { int ridnew = getReplyIDIncrement(mid); Date ts = Date.from(Instant.now()); getJdbcTemplate().update("INSERT INTO replies(message_id, reply_id, user_id, replyto, attach, txt, ts) " + "VALUES (?, ?, ?, ?, ?, ?, ?)", - mid, ridnew, uid, rid, attachment, txt, ts); + mid, ridnew, user.getUid(), rid, attachment, txt, ts); if (ridnew > 0) { getJdbcTemplate().update( "UPDATE messages SET replies = replies + 1, updated=? WHERE message_id = ?", ts, mid); + setLastReadComment(user, mid, ridnew); } return ridnew; } @@ -771,8 +772,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Transactional(readOnly = true) @Override - public List getReplies(final int mid) { - return getNamedParameterJdbcTemplate().query( + public List getReplies(final User user, final int mid) { + List replies = getNamedParameterJdbcTemplate().query( "SELECT replies.message_id as mid, replies.reply_id, replies.replyto, " + "replies.user_id, users.nick, users.banned, " + "TIMESTAMPDIFF(MINUTE, replies.ts, NOW()), replies.ts, " + @@ -793,6 +794,10 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "WHERE replies.message_id = :mid ORDER BY replies.reply_id ASC", new MapSqlParameterSource("mid", mid), new MessageMapper()); + if (replies.size() > 0) { + setLastReadComment(user, mid, replies.stream().map(Message::getRid).max(Comparator.naturalOrder()).get()); + } + return replies; } @Transactional @@ -919,4 +924,10 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "WHERE COALESCE(messages_tags.tag_id, 0) != 2 AND favorites.ts > TIMESTAMPADD(HOUR, -2, CURRENT_TIMESTAMP) " + "AND messages.popular=0 GROUP BY messages.message_id HAVING COUNT(DISTINCT favorites.user_id) > 1;", Integer.class); } + @Transactional + @Override + public void setLastReadComment(User user, Integer mid, Integer rid) { + jdbcTemplate.update("UPDATE subscr_messages SET last_read_rid=? WHERE message_id=? AND suser_id=?", + rid, mid, user.getUid()); + } } diff --git a/juick-server-jdbc/src/main/java/com/juick/service/SubscriptionServiceImpl.java b/juick-server-jdbc/src/main/java/com/juick/service/SubscriptionServiceImpl.java index 0273c1c3..d580f97a 100644 --- a/juick-server-jdbc/src/main/java/com/juick/service/SubscriptionServiceImpl.java +++ b/juick-server-jdbc/src/main/java/com/juick/service/SubscriptionServiceImpl.java @@ -41,22 +41,12 @@ import java.util.stream.Collectors; */ @Repository public class SubscriptionServiceImpl extends BaseJdbcService implements SubscriptionService { - private final UserService userService; - private final MessagesService messagesService; - private final TagService tagService; - @Inject - public SubscriptionServiceImpl(UserService userService, - MessagesService messagesService, TagService tagService) { - Assert.notNull(userService, "UserService must be initialized"); - this.userService = userService; - - Assert.notNull(messagesService,"MessagesService must be initialized"); - this.messagesService = messagesService; - - Assert.notNull(tagService, "TagService must be initialized"); - this.tagService = tagService; - } + private UserService userService; + @Inject + private MessagesService messagesService; + @Inject + private TagService tagService; @Transactional(readOnly = true) @Override diff --git a/juick-server-jdbc/src/main/resources/schema.sql b/juick-server-jdbc/src/main/resources/schema.sql index 544b4e51..66b4d7ac 100644 --- a/juick-server-jdbc/src/main/resources/schema.sql +++ b/juick-server-jdbc/src/main/resources/schema.sql @@ -227,6 +227,7 @@ CREATE TABLE IF NOT EXISTS `replies` ( CREATE TABLE IF NOT EXISTS `subscr_messages` ( `message_id` int(10) unsigned NOT NULL, `suser_id` int(10) unsigned NOT NULL, + `last_read_rid` smallint(5) unsigned NOT NULL DEFAULT '0', UNIQUE KEY (`message_id`,`suser_id`) ); diff --git a/juick-server/src/main/java/com/juick/server/api/Messages.java b/juick-server/src/main/java/com/juick/server/api/Messages.java index 86426bf6..45b8f69c 100644 --- a/juick-server/src/main/java/com/juick/server/api/Messages.java +++ b/juick-server/src/main/java/com/juick/server/api/Messages.java @@ -145,7 +145,7 @@ public class Messages { if (!messagesService.canViewThread(mid, vuid)) { return FORBIDDEN; } else { - List replies = messagesService.getReplies(mid); + List replies = messagesService.getReplies(visitor, mid); replies.add(0, msg); return ResponseEntity.ok(replies); } diff --git a/juick-server/src/test/java/com/juick/server/tests/ServerTests.java b/juick-server/src/test/java/com/juick/server/tests/ServerTests.java index 2c335cb2..095566ec 100644 --- a/juick-server/src/test/java/com/juick/server/tests/ServerTests.java +++ b/juick-server/src/test/java/com/juick/server/tests/ServerTests.java @@ -54,22 +54,18 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.Stanza; import rocks.xmpp.core.stanza.model.StanzaError; import rocks.xmpp.core.stanza.model.client.ClientMessage; import rocks.xmpp.core.stanza.model.errors.Condition; -import rocks.xmpp.core.stanza.model.server.ServerMessage; import javax.inject.Inject; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.sql.Timestamp; import java.time.Instant; import java.util.*; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.IntStream; @@ -246,18 +242,18 @@ public class ServerTests { assertEquals("we already have ugnich", -1, userService.createUser("ugnich", "x")); int ugnich_id = userService.createUser("hugnich", "x"); User ugnich = userService.getUserByUID(ugnich_id).orElse(new User()); - int rid = messagesService.createReply(msg2.getMid(), 0, ugnich.getUid(), "bla-bla", null); + int rid = messagesService.createReply(msg2.getMid(), 0, ugnich, "bla-bla", null); assertEquals(1, rid); assertThat(msg2.getTo(), equalTo(null)); Message reply = messagesService.getReply(msg2.getMid(), rid); assertThat(reply.getTo().getName(), equalTo(user.getName())); - List replies = messagesService.getReplies(msg2.getMid()); + List replies = messagesService.getReplies(user, msg2.getMid()); assertThat(replies.size(), equalTo(1)); assertThat(replies.get(0), equalTo(reply)); - int ridToReply = messagesService.createReply(msg2.getMid(), 1, ugnich_id, "blax2", null); + int ridToReply = messagesService.createReply(msg2.getMid(), 1, ugnich, "blax2", null); Message reply2 = messagesService.getReply(msg2.getMid(), ridToReply); assertThat(reply.getTo().getName(), equalTo(user.getName())); - List replies2 = messagesService.getReplies(msg2.getMid()); + List replies2 = messagesService.getReplies(user, msg2.getMid()); assertThat(replies2.size(), equalTo(2)); assertThat(replies2.get(1), equalTo(reply2)); Message msg3 = messagesService.getMessage(mid2); @@ -267,7 +263,7 @@ public class ServerTests { assertEquals(-1, userService.checkPassword(ugnich.getName(), "xy")); subscriptionService.subscribeMessage(msg.getMid(), user.getUid()); subscriptionService.subscribeMessage(msg.getMid(), ugnich.getUid()); - int reply_id = messagesService.createReply(msg.getMid(), 0, ugnich_id, "comment", null); + int reply_id = messagesService.createReply(msg.getMid(), 0, ugnich, "comment", null); assertEquals(1, subscriptionService.getUsersSubscribedToComments(msg, messagesService.getReply(msg.getMid(), reply_id)).size()); assertThat(messagesService.getDiscussions(ugnich.getUid(), 0L).get(0), @@ -308,7 +304,7 @@ public class ServerTests { Instant ts = jdbcTemplate.queryForObject("SELECT updated FROM messages WHERE message_id=?", Timestamp.class, mid).toInstant(); Thread.sleep(1000); - int rid = messagesService.createReply(mid, 0, ugnich_id, "people", null); + int rid = messagesService.createReply(mid, 0, ugnich, "people", null); Instant rts = jdbcTemplate.queryForObject("SELECT updated FROM messages WHERE message_id=?", Timestamp.class, mid).toInstant(); assertThat(rts, greaterThan(ts)); @@ -498,7 +494,9 @@ public class ServerTests { public void topTest() { int topmid = messagesService.createMessage(ugnich.getUid(), "top message", null, null); IntStream.rangeClosed(6, 12).forEach(i -> { - messagesService.createReply(topmid, 0, i, "yo", null); + User next = new User(); + next.setUid(i); + messagesService.createReply(topmid, 0, next, "yo", null); }); assertThat(messagesService.getPopularCandidates().get(0), is(topmid)); @@ -615,7 +613,7 @@ public class ServerTests { commandsManager.processCommand(readerUser, "S #" + mid, emptyUri).getText()); assertEquals("should be favorited", "Message is added to your recommendations", commandsManager.processCommand(readerUser, "! #" + mid, emptyUri).getText()); - int rid = messagesService.createReply(mid, 0, uid, "comment", null); + int rid = messagesService.createReply(mid, 0, user, "comment", null); assertEquals("number of subscribed users should match", 1, subscriptionService.getUsersSubscribedToComments( messagesService.getMessage(mid), @@ -642,7 +640,7 @@ public class ServerTests { assertEquals("should be third reply", expectedThirdReply, commandsManager.processCommand(user, "#" + mid + "/2 ", URI.create("http://static.juick.com/settings/facebook.png")).getText()); - Message reply = messagesService.getReplies(mid).stream().filter(m -> m.getRid() == 3).findFirst() + Message reply = messagesService.getReplies(user, mid).stream().filter(m -> m.getRid() == 3).findFirst() .orElse(new Message()); assertEquals("should be reply to second comment", 2, reply.getReplyto()); assertEquals("tags should NOT be updated", "It is not your message", diff --git a/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java b/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java index e6662c4e..5bbea56b 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java +++ b/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java @@ -540,7 +540,7 @@ public class MessagesWWW { model.addAttribute("visitorSubscribed", messagesService.isSubscribed(visitor.getUid(), msg.getMid())); model.addAttribute("visitorInBL", userService.isInBL(msg.getUser().getUid(), visitor.getUid())); model.addAttribute("recomm", messagesService.getMessageRecommendations(msg.getMid())); - List replies = messagesService.getReplies(msg.getMid()); + List replies = messagesService.getReplies(visitor, msg.getMid()); List blUIDs = new ArrayList<>(); for (Message reply : replies) { diff --git a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java index 042a1b0f..6b97c7c4 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java +++ b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java @@ -147,7 +147,7 @@ public class NewMessage { } String attachmentType = StringUtils.isNotEmpty(attachmentFName.toString()) ? attachmentFName.toString().substring(attachmentFName.toString().length() - 3) : null; - int ridnew = messagesService.createReply(mid, rid, visitor.getUid(), body, attachmentType); + int ridnew = messagesService.createReply(mid, rid, visitor, body, attachmentType); subscriptionService.subscribeMessage(mid, visitor.getUid()); Message xmsg = new Message(); diff --git a/juick-www/src/test/java/com/juick/WebAppTests.java b/juick-www/src/test/java/com/juick/WebAppTests.java index e383a0a9..37798d08 100644 --- a/juick-www/src/test/java/com/juick/WebAppTests.java +++ b/juick-www/src/test/java/com/juick/WebAppTests.java @@ -167,7 +167,7 @@ public class WebAppTests { public void repliesList() throws IOException { int mid = messagesService.createMessage(ugnich.getUid(), "hello", null, null); IntStream.range(1, 15).forEach(i -> - messagesService.createReply(mid, i-1, freefd.getUid(), String.valueOf(i-1), null )); + messagesService.createReply(mid, i-1, freefd, String.valueOf(i-1), null )); HtmlPage threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getWebResponse().getStatusCode(), equalTo(200)); @@ -181,7 +181,7 @@ public class WebAppTests { @Test public void userShouldNotSeeReplyButtonToBannedUser() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "freefd bl me", null, null); - messagesService.createReply(mid, 0, ugnich.getUid(), "yo", null); + messagesService.createReply(mid, 0, ugnich, "yo", null); MvcResult loginResult = mockMvc.perform(post("/login") .param("username", freefdName) .param("password", freefdPassword)).andReturn(); @@ -198,7 +198,8 @@ public class WebAppTests { privacyQueriesService.blacklistUser(freefd, ugnich); assertThat(userService.isInBLAny(freefd.getUid(), ugnich.getUid()), equalTo(true)); int renhaId = userService.createUser("renha", "secret"); - messagesService.createReply(mid, 0, renhaId, "people", null); + messagesService.createReply(mid, 0, userService.getUserByUID(renhaId).orElseThrow(IllegalStateException::new), + "people", null); threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getWebResponse().getStatusCode(), equalTo(200)); assertThat(threadPage.querySelectorAll(".msg-comment-target").isEmpty(), equalTo(true)); @@ -292,7 +293,7 @@ public class WebAppTests { .param("body", String.format("D #%d/%d", mid, 3))) .andExpect(status().isFound()); Thread.sleep(5000); - assertThat(messagesService.getReplies(mid).size(), equalTo(2)); + assertThat(messagesService.getReplies(ugnich, mid).size(), equalTo(2)); } @Test public void hashLoginShouldNotUseSession() throws Exception { @@ -342,7 +343,7 @@ public class WebAppTests { 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); + messagesService.createReply(mid, 0, freefd, "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))); @@ -358,7 +359,7 @@ public class WebAppTests { 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); + messagesService.createReply(midNew, 0, freefd, "I'm replied", null); discussions = (HtmlPage) discussions.refresh(); assertThat(discussions.querySelectorAll("article") .get(0).getAttributes().getNamedItem("data-mid").getNodeValue(), is(String.valueOf(midNew))); -- cgit v1.2.3