From 2c1bfab10903895ece9644bc095597aaef2a75e8 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 14 Aug 2018 13:23:53 +0300 Subject: Message editing API --- juick-server/build.gradle | 2 ++ .../src/main/java/com/juick/server/api/Post.java | 17 +++++++++ .../com/juick/service/MessagesServiceImpl.java | 41 +++++++++++++++------- .../db/migration/V1.1__Add updated_at field.sql | 2 ++ juick-server/src/main/resources/schema.sql | 23 ++++++++++-- juick-server/src/main/resources/update.sql | 12 ------- .../java/com/juick/server/tests/ServerTests.java | 38 ++++++++++++++++++++ 7 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 juick-server/src/main/resources/db/migration/V1.1__Add updated_at field.sql delete mode 100644 juick-server/src/main/resources/update.sql (limited to 'juick-server') diff --git a/juick-server/build.gradle b/juick-server/build.gradle index d271ef69..a3ccce31 100644 --- a/juick-server/build.gradle +++ b/juick-server/build.gradle @@ -34,6 +34,8 @@ dependencies { compile 'com.rometools:rome:1.11.0' compile 'com.rometools:rome-modules:1.11.0' + compile 'org.flywaydb:flyway-core:5.1.4' + providedRuntime 'mysql:mysql-connector-java:5.1.45' testCompile("org.springframework.boot:spring-boot-starter-test") diff --git a/juick-server/src/main/java/com/juick/server/api/Post.java b/juick-server/src/main/java/com/juick/server/api/Post.java index afe9d2d5..b0be50b6 100644 --- a/juick-server/src/main/java/com/juick/server/api/Post.java +++ b/juick-server/src/main/java/com/juick/server/api/Post.java @@ -17,6 +17,7 @@ package com.juick.server.api; +import com.juick.Message; import com.juick.Reaction; import com.juick.Status; import com.juick.User; @@ -202,4 +203,20 @@ public class Post { return recommendStatus == MessagesService.RecommendStatus.Error ? Status.ERROR :Status.OK; } + + @PostMapping("/update") + public CommandResult updateMessage(@RequestParam Integer mid, + @RequestParam(required = false, defaultValue = "0") Integer rid, + @RequestParam String body) { + User visitor = UserUtils.getCurrentUser(); + User author = rid == 0 ? messagesService.getMessageAuthor(mid) : messagesService.getReply(mid, rid).getUser(); + if (visitor.equals(author)) { + if (messagesService.updateMessage(mid, rid, body)) { + Message result = rid == 0 ? messagesService.getMessage(mid) : messagesService.getReply(mid, rid); + return CommandResult.build(result, "Message updated", StringUtils.EMPTY); + } + throw new HttpBadRequestException(); + } + throw new HttpForbiddenException(); + } } diff --git a/juick-server/src/main/java/com/juick/service/MessagesServiceImpl.java b/juick-server/src/main/java/com/juick/service/MessagesServiceImpl.java index ba721eea..807f4a9d 100644 --- a/juick-server/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/juick-server/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -92,6 +92,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ quoteUser.setName(rs.getString(20)); msg.setTo(quoteUser); } + msg.setUpdatedAt(rs.getTimestamp(21).toInstant()); if (StringUtils.isNotEmpty(msg.getAttachmentType())) { try { imagesService.setAttachmentMetadata(baseImagesUrl, msg); @@ -148,9 +149,9 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ } getJdbcTemplate().update( - "INSERT INTO messages_txt(message_id, tags, txt) VALUES (?, ?, ?)", - new Object[]{mid, tagsNames, txt}, - new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR}); + "INSERT INTO messages_txt(message_id, tags, txt, updated_at) VALUES (?, ?, ?, ?)", + new Object[]{mid, tagsNames, txt, Timestamp.from(now)}, + new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP}); getJdbcTemplate().update("UPDATE users SET lastmessage=? where id=?", Timestamp.from(now), uid); } @@ -171,9 +172,9 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ 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, user.getUid(), rid, attachment, txt, ts); + getJdbcTemplate().update("INSERT INTO replies(message_id, reply_id, user_id, replyto, attach, txt, ts, updated_at) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + mid, ridnew, user.getUid(), rid, attachment, txt, ts, ts); if (ridnew > 0) { getJdbcTemplate().update( @@ -328,8 +329,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ + "messages.ts," + "messages.readonly, messages.privacy, messages.replies," + "messages.attach, COUNT(DISTINCT favorites.user_id) as likes, messages.hidden," - + "txt.tags, txt.repliesby, txt.txt, '' as q, messages.updated, 0 as to_uid, " - + "NULL as to_name FROM messages " + + "txt.tags, txt.repliesby, txt.txt, '' as q, messages.updated as updated, 0 as to_uid, " + + "NULL as to_name, txt.updated_at FROM messages " + "INNER JOIN users ON messages.user_id = users.id " + "INNER JOIN messages_txt AS txt " + "ON messages.message_id = txt.message_id " @@ -337,7 +338,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ + "ON messages.message_id = favorites.message_id AND favorites.like_id=1 " + "WHERE messages.message_id = ? " + "GROUP BY mid, rid, replyto, uid, nick, banned, messages.ts, readonly, " - + "privacy, replies, attach, tags, repliesby, q", + + "privacy, replies, attach, tags, repliesby, q, updated_at", new MessageMapper(), mid); if (!list.isEmpty()) { @@ -356,7 +357,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "SELECT replies.user_id, users.nick," + "replies.replyto, replies.ts," + "replies.attach, replies.txt, IFNULL(q.txt,t.txt) as quote, " - + "COALESCE(q.user_id, m.user_id) AS to_uid, COALESCE(qu.nick, mu.nick) AS to_name " + + "COALESCE(q.user_id, m.user_id) AS to_uid, COALESCE(qu.nick, mu.nick) AS to_name, " + + "replies.updated_at " + "FROM replies INNER JOIN users ON replies.user_id = users.id " + "LEFT JOIN replies q ON replies.message_id = q.message_id and replies.replyto = q.reply_id " + "LEFT JOIN messages_txt t ON replies.message_id = t.message_id " @@ -388,6 +390,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ quoteUser.setName(rs.getString(9)); msg.setTo(quoteUser); } + msg.setUpdatedAt(rs.getTimestamp(10).toInstant()); return msg; }, @@ -776,7 +779,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ + "messages.readonly,messages.privacy, messages.replies-COUNT(DISTINCT banned.reply_id) as replies," + "messages.attach,COUNT(DISTINCT favorites.user_id) AS likes,messages.hidden," + "messages_txt.tags,messages_txt.repliesby, messages_txt.txt, '' as q, " - + "messages.updated, 0 as to_uid, NULL as to_name " + + "messages.updated, 0 as to_uid, NULL as to_name, messages_txt.updated_at " + "FROM (messages INNER JOIN messages_txt " + "ON messages.message_id=messages_txt.message_id) " + "INNER JOIN users ON messages.user_id=users.id " @@ -852,7 +855,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "NULL as tags, NULL as repliesby, replies.txt, " + "IFNULL(qw.txt, t.txt) as q, " + "NOW(), " + - "COALESCE(qw.user_id, m.user_id) as to_uid, COALESCE(qu.nick, mu.nick) as to_name " + + "COALESCE(qw.user_id, m.user_id) as to_uid, COALESCE(qu.nick, mu.nick) as to_name, " + + "replies.updated_at " + "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 " + @@ -1011,6 +1015,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ mid, user.getUid()); } + @Transactional(readOnly = true) @Override public List getUnread(User user) { return jdbcTemplate.queryForList( @@ -1020,4 +1025,16 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ "messages.replies>subscr_messages.last_read_rid", Integer.class, user.getUid()); } + + @Transactional + @Override + public boolean updateMessage(Integer mid, Integer rid, String body) { + Instant now = Instant.now(); + if (rid == 0) { + return jdbcTemplate.update("UPDATE messages_txt SET txt=?, updated_at=? WHERE message_id=?", body, Timestamp.from(now), mid) > 0; + } else { + return jdbcTemplate.update("UPDATE replies SET txt=?, updated_at=? WHERE message_id=? and reply_id=?", + body, Timestamp.from(now), mid, rid) > 0; + } + } } diff --git a/juick-server/src/main/resources/db/migration/V1.1__Add updated_at field.sql b/juick-server/src/main/resources/db/migration/V1.1__Add updated_at field.sql new file mode 100644 index 00000000..dac179b1 --- /dev/null +++ b/juick-server/src/main/resources/db/migration/V1.1__Add updated_at field.sql @@ -0,0 +1,2 @@ +ALTER TABLE messages_txt ADD COLUMN updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE replies ADD COLUMN updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/juick-server/src/main/resources/schema.sql b/juick-server/src/main/resources/schema.sql index 296fd486..851b764b 100644 --- a/juick-server/src/main/resources/schema.sql +++ b/juick-server/src/main/resources/schema.sql @@ -326,7 +326,7 @@ CREATE TABLE IF NOT EXISTS `messages` ( `message_id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `user_id` int(10) unsigned NOT NULL, `lang` enum('en','ru','fr','fa','__') NOT NULL DEFAULT '__', - `ts` timestamp(9) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `replies` smallint(5) unsigned NOT NULL DEFAULT '0', `maxreplyid` smallint(5) unsigned NOT NULL DEFAULT '0', `privacy` tinyint(4) NOT NULL DEFAULT '1', @@ -338,7 +338,7 @@ CREATE TABLE IF NOT EXISTS `messages` ( `popular` tinyint(4) NOT NULL DEFAULT '0', `hidden` tinyint(3) unsigned NOT NULL DEFAULT '0', `likes` smallint(6) NOT NULL DEFAULT '0', - `updated` timestamp(9) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) references users(id) ); CREATE TABLE IF NOT EXISTS `pm_inroster` ( @@ -376,4 +376,21 @@ CREATE TABLE IF NOT EXISTS `wl_users` ( `user_id` int(10) unsigned NOT NULL, `wl_user_id` int(10) unsigned NOT NULL, PRIMARY KEY (`user_id`,`wl_user_id`) -); \ No newline at end of file +); + +CREATE CACHED TABLE PUBLIC."flyway_schema_history" ( + "installed_rank" INT NOT NULL, + "version" VARCHAR(50), + "description" VARCHAR(200) NOT NULL, + "type" VARCHAR(20) NOT NULL, + "script" VARCHAR(1000) NOT NULL, + "checksum" INT, + "installed_by" VARCHAR(100) NOT NULL, + "installed_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP() NOT NULL, + "execution_time" INT NOT NULL, + "success" BOOLEAN NOT NULL +); +ALTER TABLE PUBLIC."flyway_schema_history" ADD CONSTRAINT PUBLIC."flyway_schema_history_pk" PRIMARY KEY("installed_rank"); +-- 1 +/- SELECT COUNT(*) FROM PUBLIC."flyway_schema_history"; +INSERT INTO PUBLIC."flyway_schema_history"("installed_rank", "version", "description", "type", "script", "checksum", "installed_by", "installed_on", "execution_time", "success") VALUES +(1, '1', '<< Flyway Baseline >>', 'BASELINE', '<< Flyway Baseline >>', NULL, 'SA', TIMESTAMP '2018-08-14 13:05:13.724', 0, TRUE); \ No newline at end of file diff --git a/juick-server/src/main/resources/update.sql b/juick-server/src/main/resources/update.sql deleted file mode 100644 index 13a62c3d..00000000 --- a/juick-server/src/main/resources/update.sql +++ /dev/null @@ -1,12 +0,0 @@ --- if version table not exists set up version = 0; -update version set version = 0; - -DROP TABLE IF EXISTS `version`; - -CREATE TABLE `version` ( - `version` bigint NOT NULL -); - -insert into version values (0); - -update version set version = 1; \ No newline at end of file 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 1aff9ca3..2d28dc9d 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 @@ -1130,4 +1130,42 @@ public class ServerTests { Files.deleteIfExists(pngOutput); } } + @Test + public void messageEditingSpec() throws Exception { + MvcResult result = mockMvc.perform(post("/post").with(httpBasic(ugnichName, ugnichPassword)) + .param("body", "YO")).andExpect(status().is2xxSuccessful()).andReturn(); + Message original = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) + .getNewMessage().get(); + assertThat(original.getText(), equalTo("YO")); + assertThat(original.getUpdatedAt(), equalTo(original.getTimestamp())); + // to have updated_at greater than ts + Thread.sleep(1000); + result = mockMvc.perform(post("/update").with(httpBasic(ugnichName, ugnichPassword)) + .param("mid", String.valueOf(original.getMid())) + .param("body", "PEOPLE")).andExpect(status().is2xxSuccessful()).andReturn(); + Message edited = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) + .getNewMessage().get(); + assertThat(edited.getText(), equalTo("PEOPLE")); + assertThat(edited.getUpdatedAt(), greaterThan(edited.getTimestamp())); + mockMvc.perform(post("/update").with(httpBasic(freefdName, freefdPassword)) + .param("mid", String.valueOf(original.getMid())) + .param("body", "PEOPLE")).andExpect(status().is(403)); + result = mockMvc.perform(post("/comment").with(httpBasic(freefdName, freefdPassword)) + .param("mid", String.valueOf(original.getMid())) + .param("body", "HEY")).andExpect(status().is2xxSuccessful()).andReturn(); + Message comment = jsonMapper.readValue(result.getResponse().getContentAsString(), Message.class); + assertThat(comment.getText(), is("HEY")); + assertThat(comment.getUpdatedAt(), is(comment.getTimestamp())); + // to have updated_at greater than ts + Thread.sleep(1000); + result = mockMvc.perform(post("/update").with(httpBasic(freefdName, freefdPassword)) + .param("mid", String.valueOf(comment.getMid())) + .param("rid", String.valueOf(comment.getRid())) + .param("body", "HEY, JOE")).andExpect(status().is2xxSuccessful()).andReturn(); + Message editedComment = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) + .getNewMessage().get(); + assertThat(editedComment.getText(), is("HEY, JOE")); + assertThat(editedComment.getUpdatedAt(), greaterThan(editedComment.getTimestamp())); + messagesService.deleteMessage(ugnich.getUid(), original.getMid()); + } } -- cgit v1.2.3