aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--juick-common/src/main/java/com/juick/Message.java18
-rw-r--r--juick-common/src/main/java/com/juick/service/MessagesService.java3
-rw-r--r--juick-server/build.gradle2
-rw-r--r--juick-server/src/main/java/com/juick/server/api/Post.java17
-rw-r--r--juick-server/src/main/java/com/juick/service/MessagesServiceImpl.java41
-rw-r--r--juick-server/src/main/resources/db/migration/V1.1__Add updated_at field.sql2
-rw-r--r--juick-server/src/main/resources/schema.sql23
-rw-r--r--juick-server/src/main/resources/update.sql12
-rw-r--r--juick-server/src/test/java/com/juick/server/tests/ServerTests.java38
9 files changed, 129 insertions, 27 deletions
diff --git a/juick-common/src/main/java/com/juick/Message.java b/juick-common/src/main/java/com/juick/Message.java
index 3800f466..212cb2fa 100644
--- a/juick-common/src/main/java/com/juick/Message.java
+++ b/juick-common/src/main/java/com/juick/Message.java
@@ -46,6 +46,7 @@ public class Message implements Comparable {
private final List<Tag> tags;
private Instant ts;
private Instant updated;
+ private Instant updatedAt;
private boolean unread;
@JsonIgnore
private int privacy = 1;
@@ -281,6 +282,9 @@ public class Message implements Comparable {
this.attachment = attachment;
}
+ /**
+ * @return timestamp of the last comment
+ */
@XmlTransient
public Instant getUpdated() {
return updated;
@@ -329,4 +333,18 @@ public class Message implements Comparable {
public void setRecommendations(Set<String> recommendations) {
this.recommendations = recommendations;
}
+
+ /**
+ * @return timestamp of the last edit
+ */
+ @XmlTransient
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
+ @JsonProperty("updated_at")
+ public Instant getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Instant updatedAt) {
+ this.updatedAt = updatedAt;
+ }
}
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 28f59153..202e967e 100644
--- a/juick-common/src/main/java/com/juick/service/MessagesService.java
+++ b/juick-common/src/main/java/com/juick/service/MessagesService.java
@@ -17,6 +17,7 @@
package com.juick.service;
+import com.juick.Message;
import com.juick.Reaction;
import com.juick.User;
import com.juick.server.helpers.ResponseReply;
@@ -126,4 +127,6 @@ public interface MessagesService {
void setRead(User user, Integer mid);
List<Integer> getUnread(User user);
+
+ boolean updateMessage(Integer mid, Integer rid, String body);
}
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<Integer> 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());
+ }
}