aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/juick/service/BaseJdbcService.java80
-rw-r--r--src/main/java/com/juick/service/ChatServiceImpl.java11
-rw-r--r--src/main/java/com/juick/service/MessagesServiceImpl.java119
-rw-r--r--src/main/java/com/juick/service/TagServiceImpl.java17
-rw-r--r--src/main/java/com/juick/service/UserServiceImpl.java19
-rw-r--r--src/main/java/com/juick/util/PrettyTimeFormatter.java3
-rw-r--r--src/main/java/com/juick/www/api/Messages.java3
-rw-r--r--src/main/java/com/juick/www/controllers/Site.java3
-rw-r--r--src/main/java/com/mitchellbosecke/pebble/extension/FormatterExtension.java1
-rw-r--r--src/main/java/com/mitchellbosecke/pebble/extension/filters/PrettyTimeFilter.java2
-rw-r--r--src/main/java/com/mitchellbosecke/pebble/extension/filters/TimestampFilter.java42
-rw-r--r--src/main/resources/data-mysql.sql13
-rw-r--r--src/main/resources/data-sqlite.sql22
-rw-r--r--src/main/resources/schema-sqlite.sql231
-rw-r--r--src/main/resources/templates/views/partial/message.html4
-rw-r--r--src/main/resources/templates/views/thread.html8
16 files changed, 350 insertions, 228 deletions
diff --git a/src/main/java/com/juick/service/BaseJdbcService.java b/src/main/java/com/juick/service/BaseJdbcService.java
index 51c41251..d6f29283 100644
--- a/src/main/java/com/juick/service/BaseJdbcService.java
+++ b/src/main/java/com/juick/service/BaseJdbcService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2020, Juick
+ * Copyright (C) 2008-2023, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,10 +17,20 @@
package com.juick.service;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.inject.Inject;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
/**
* Created by aalexeev on 11/13/16.
@@ -30,6 +40,19 @@ public class BaseJdbcService {
JdbcTemplate jdbcTemplate;
@Inject
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+ @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlserver')}")
+ protected boolean omitRecursiveKeyword;
+ @Value("#{('${spring.sql.init.platform}' == 'sqlite') or ('${spring.sql.init.platform}' == 'mysql')}")
+ // Added in MariaDB 10.6
+ protected boolean haveNoANSIFetch;
+ @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlite')}")
+ protected boolean haveNoDates;
+ @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlite')}")
+ protected boolean haveNoGreatest;
+ @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlite')}")
+ protected boolean haveNoForUpdate;
+ @Value("#{new Boolean('${spring.sql.init.platform}' == 'mysql')}")
+ protected boolean haveNoOffsetDateTime;
public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
return namedParameterJdbcTemplate;
@@ -38,4 +61,59 @@ public class BaseJdbcService {
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
+ protected String limit(int rows) {
+ if (haveNoANSIFetch) {
+ return "LIMIT " + rows;
+ } else {
+ return "OFFSET 0 ROWS FETCH NEXT " + rows + " ROWS ONLY";
+ }
+ }
+ protected String greatest() {
+ if (haveNoGreatest) {
+ return "MAX";
+ }
+ return "GREATEST";
+ }
+ protected String forUpdate() {
+ if (haveNoForUpdate) {
+ return "";
+ }
+ return "FOR UPDATE";
+ }
+ public OffsetDateTime getOffsetDateTime(ResultSet rs, int columnIndex) throws SQLException {
+ if (haveNoDates) {
+ var date = rs.getString(columnIndex);
+ if (StringUtils.isNotEmpty(date)) {
+ return OffsetDateTime.parse(date);
+ }
+ return null;
+ }
+ if (haveNoOffsetDateTime) {
+ var date = rs.getTimestamp(columnIndex);
+ if (date != null) {
+ return date.toInstant().atOffset(ZoneOffset.UTC);
+ }
+ return null;
+ }
+ return rs.getObject(columnIndex, OffsetDateTime.class);
+ }
+ public Object fromEpochMilli(Long milliseconds) {
+ if (haveNoDates) {
+ return Instant.ofEpochMilli(milliseconds).atOffset(ZoneOffset.UTC)
+ .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ }
+ if (haveNoOffsetDateTime) {
+ return new Timestamp(milliseconds);
+ }
+ return Instant.ofEpochMilli(milliseconds).atOffset(ZoneOffset.UTC);
+ }
+ public int dateTimeType() {
+ if (haveNoDates) {
+ return Types.VARCHAR;
+ }
+ if (haveNoOffsetDateTime) {
+ return Types.TIMESTAMP;
+ }
+ return Types.TIMESTAMP_WITH_TIMEZONE;
+ }
}
diff --git a/src/main/java/com/juick/service/ChatServiceImpl.java b/src/main/java/com/juick/service/ChatServiceImpl.java
index d1c4ce96..c0d2f17b 100644
--- a/src/main/java/com/juick/service/ChatServiceImpl.java
+++ b/src/main/java/com/juick/service/ChatServiceImpl.java
@@ -56,7 +56,7 @@ public class ChatServiceImpl extends BaseJdbcService implements ChatService {
Chat u = new Chat();
u.setUid(rs.getInt(1));
u.setName(rs.getString(2));
- u.setLastMessageTimestamp(rs.getTimestamp(3).toInstant());
+ u.setLastMessageTimestamp(getOffsetDateTime(rs,3).toInstant());
u.setLastMessageText(rs.getString(4).trim());
return u;
},
@@ -72,7 +72,7 @@ public class ChatServiceImpl extends BaseJdbcService implements ChatService {
return getNamedParameterJdbcTemplate().query(
"SELECT pm.user_id, pm.txt, pm.ts, users.nick FROM pm INNER JOIN users ON users.id=pm.user_id WHERE (user_id = :uid AND user_id_to = :uidTo) "
- + "OR (user_id_to = :uid AND user_id = :uidTo) ORDER BY ts DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + "OR (user_id_to = :uid AND user_id = :uidTo) ORDER BY ts DESC " + limit(20),
sqlParameterSource,
(rs, rowNum) -> {
Message msg = new Message();
@@ -82,7 +82,7 @@ public class ChatServiceImpl extends BaseJdbcService implements ChatService {
user.setName(rs.getString(4));
msg.setUser(user);
msg.setText(rs.getString(2).trim());
- msg.setCreated(rs.getTimestamp(3).toInstant());
+ msg.setCreated(getOffsetDateTime(rs,3).toInstant());
return msg;
});
}
@@ -92,7 +92,8 @@ public class ChatServiceImpl extends BaseJdbcService implements ChatService {
public List<Message> getInbox(final int uid) {
return getJdbcTemplate().query(
"SELECT pm.user_id, users.nick, pm.txt, pm.ts " +
- "FROM pm INNER JOIN users ON pm.user_id=users.id WHERE pm.user_id_to=? ORDER BY pm.ts DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ "FROM pm INNER JOIN users ON pm.user_id=users.id WHERE pm.user_id_to=? ORDER BY pm.ts DESC " +
+ limit(20),
(rs, num) -> {
Message msg = new Message();
msg.setUser(new User());
@@ -111,7 +112,7 @@ public class ChatServiceImpl extends BaseJdbcService implements ChatService {
return getJdbcTemplate().query(
"SELECT pm.user_id_to, users.nick, pm.txt, " +
"pm.ts FROM pm INNER JOIN users ON pm.user_id_to=users.id " +
- "WHERE pm.user_id=? ORDER BY pm.ts DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ "WHERE pm.user_id=? ORDER BY pm.ts DESC " + limit(20),
(rs, num) -> {
Message msg = new Message();
msg.setUser(new User());
diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java
index 8149f37f..112fc5bf 100644
--- a/src/main/java/com/juick/service/MessagesServiceImpl.java
+++ b/src/main/java/com/juick/service/MessagesServiceImpl.java
@@ -45,6 +45,7 @@ import javax.inject.Inject;
import java.net.URI;
import java.sql.*;
import java.time.Instant;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
@@ -70,8 +71,6 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
private String baseImagesUrl;
@Inject
private User archiveUser;
- @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlserver')}")
- private boolean omitRecursiveKeyword;
private class MessageMapper implements RowMapper<Message> {
@Override
@@ -86,7 +85,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
user.setBanned(rs.getBoolean(6));
user.setUri(URI.create(Optional.ofNullable(rs.getString(22)).orElse(StringUtils.EMPTY)));
msg.setUser(user);
- msg.setCreated(rs.getTimestamp(7).toInstant());
+ msg.setCreated(MessagesServiceImpl.this.getOffsetDateTime(rs,7).toInstant());
msg.ReadOnly = rs.getBoolean(8);
msg.setPrivacy(rs.getInt(9));
msg.FriendsOnly = msg.getPrivacy() < 0;
@@ -100,7 +99,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
msg.setRepliesBy(rs.getString(15));
msg.setText(rs.getString(16));
msg.setReplyQuote(MessageUtils.formatQuote(rs.getString(17)));
- msg.setUpdated(rs.getTimestamp(18).toInstant());
+ msg.setUpdated(MessagesServiceImpl.this.getOffsetDateTime(rs,18).toInstant());
int quoteUid = rs.getInt(19);
User quoteUser = new User();
quoteUser.setUid(quoteUid);
@@ -112,7 +111,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
quoteUser.setAvatar(webApp.getAvatarUrl(quoteUser));
}
msg.setTo(quoteUser);
- msg.setUpdatedAt(rs.getTimestamp(21).toInstant());
+ msg.setUpdatedAt(MessagesServiceImpl.this.getOffsetDateTime(rs,21).toInstant());
msg.setReplyUri(URI.create(Optional.ofNullable(rs.getString(24)).orElse(StringUtils.EMPTY)));
msg.setHtml(rs.getBoolean(25));
msg.setUnread(rs.getInt(26) > 0);
@@ -137,11 +136,12 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
@Override
public int createMessage(final int uid, final String txt, final String attachment, @NonNull final Set<Tag> tags) {
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(getJdbcTemplate()).withTableName("messages")
- .usingColumns("user_id", "attach", "ts", "readonly").usingGeneratedKeyColumns("message_id");
+ .usingColumns("user_id", "attach", "ts", "readonly", "updated").usingGeneratedKeyColumns("message_id");
var insertMap = new MapSqlParameterSource();
insertMap.addValue("user_id", uid);
var now = Instant.now();
insertMap.addValue("ts", now.atOffset(ZoneOffset.UTC), java.sql.Types.TIMESTAMP_WITH_TIMEZONE);
+ insertMap.addValue("updated", now.atOffset(ZoneOffset.UTC), java.sql.Types.TIMESTAMP_WITH_TIMEZONE);
if (StringUtils.isNotEmpty(attachment)) {
insertMap.addValue("attach", attachment);
}
@@ -244,7 +244,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
final boolean readOnly;
final int userId;
try (PreparedStatement ps = conn.prepareStatement(
- "SELECT maxreplyid+1, readonly, user_id FROM messages WHERE message_id=? FOR UPDATE")) {
+ "SELECT maxreplyid+1, readonly, user_id FROM messages WHERE message_id=? " + forUpdate())) {
ps.setInt(1, mid);
try (ResultSet resultSet = ps.executeQuery()) {
if (resultSet.next()) {
@@ -281,7 +281,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
void updateRepliesBy(int mid) {
List<String> users = getJdbcTemplate().queryForList("SELECT users.nick FROM replies "
+ "INNER JOIN users ON replies.user_id=users.id WHERE replies.message_id=? "
- + "GROUP BY replies.user_id, users.nick ORDER BY COUNT(replies.reply_id) DESC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY", String.class,
+ + "GROUP BY replies.user_id, users.nick ORDER BY COUNT(replies.reply_id) DESC " + limit(5), String.class,
mid);
String result = users.stream().map(u -> "@" + u).collect(Collectors.joining(","));
getJdbcTemplate().update("UPDATE messages_txt SET repliesby=? WHERE message_id=?", result, mid);
@@ -450,7 +450,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
.setUri(URI.create(Optional.ofNullable(rs.getString(11)).orElse(StringUtils.EMPTY)));
}
msg.setReplyto(rs.getInt(3));
- msg.setCreated(rs.getTimestamp(4).toInstant());
+ msg.setCreated(getOffsetDateTime(rs, 4).toInstant());
msg.setAttachmentType(rs.getString(5));
msg.setText(rs.getString(6));
String quote = rs.getString(7);
@@ -464,7 +464,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
quoteUser.setName(Optional.ofNullable(rs.getString(9)).orElse(AnonymousUser.INSTANCE.getName()));
quoteUser.setUri(URI.create(Optional.ofNullable(rs.getString(12)).orElse(StringUtils.EMPTY)));
msg.setTo(quoteUser);
- msg.setUpdatedAt(rs.getTimestamp(10).toInstant());
+ msg.setUpdatedAt(getOffsetDateTime(rs, 10).toInstant());
msg.setReplyUri(URI.create(Optional.ofNullable(rs.getString(13)).orElse(StringUtils.EMPTY)));
msg.setHtml(rs.getBoolean(14));
msg.setReplyToUri(URI.create(Optional.ofNullable(rs.getString(15)).orElse(StringUtils.EMPTY)));
@@ -522,15 +522,17 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ " AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN "
+ "(SELECT tag_id FROM messages_tags WHERE message_id = m.message_id) and :visitorUid = bt.user_id)"
+ " AND NOT EXISTS (SELECT 1 from users u WHERE u.banned = 1 and u.id = m.user_id and u.id <> :visitorUid)" +
- " ORDER BY m.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ " ORDER BY m.message_id DESC " + limit(20),
sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getTag(final int tid, final int visitorUid, final int before, final int cnt) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("tid", tid).addValue("cnt", cnt)
- .addValue("before", before).addValue("visitorUid", visitorUid);
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("tid", tid)
+ .addValue("before", before)
+ .addValue("visitorUid", visitorUid);
return getNamedParameterJdbcTemplate()
.queryForList("SELECT messages.message_id FROM (tags INNER JOIN messages_tags "
@@ -539,20 +541,21 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ (before > 0 ? " messages.message_id < :before AND " : StringUtils.EMPTY)
+ "(messages.privacy > 0 OR messages.user_id = :visitorUid) "
+ "AND NOT EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :visitorUid and b.bl_user_id = messages.user_id) "
- + "ORDER BY messages.message_id DESC OFFSET 0 ROWS FETCH NEXT :cnt ROWS ONLY", sqlParameterSource, Integer.class);
+ + "ORDER BY messages.message_id DESC " + limit(cnt), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getTags(final String tids, final int visitorUid, final int before, final int cnt) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("cnt", cnt)
- .addValue("before", before).addValue("visitorUid", visitorUid);
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("before", before)
+ .addValue("visitorUid", visitorUid);
return getNamedParameterJdbcTemplate().queryForList("SELECT messages.message_id FROM messages_tags "
+ "INNER JOIN messages USING(message_id) WHERE messages_tags.tag_id IN (" + tids + ") "
+ (before > 0 ? " AND messages.message_id < :before " : StringUtils.EMPTY)
+ " AND (messages.privacy > 0 OR messages.user_id = :visitorUid) "
- + "ORDER BY messages.message_id DESC OFFSET 0 ROWS FETCH NEXT :cnt ROWS ONLY", sqlParameterSource, Integer.class);
+ + "ORDER BY messages.message_id DESC " + limit(cnt), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@@ -564,7 +567,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
return getNamedParameterJdbcTemplate().queryForList(
"SELECT message_id FROM messages WHERE place_id = :placeId "
+ (before > 0 ? " AND message_id < :before " : StringUtils.EMPTY)
- + " AND (privacy > 0 OR user_id = :visitorUid) ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + " AND (privacy > 0 OR user_id = :visitorUid) ORDER BY message_id DESC " + limit(20),
sqlParameterSource, Integer.class);
}
@@ -591,7 +594,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "AND NOT EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :uid and b.bl_user_id = messages.user_id)"
: StringUtils.EMPTY)
+ ") " + (before > 0 ? "AND message_id < :before " : StringUtils.EMPTY)
- + "ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + "ORDER BY message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@@ -603,21 +606,21 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
return getNamedParameterJdbcTemplate()
.queryForList("SELECT message_id FROM messages WHERE user_id = :uid AND privacy < 0"
+ (before > 0 ? " AND message_id < :before " : StringUtils.EMPTY)
- + "ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + "ORDER BY message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getDiscussions(final int uid, final Long to) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("uid", uid).addValue("to",
- new Timestamp(to));
-
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("uid", uid)
+ .addValue("to", fromEpochMilli(to), dateTimeType());
if (uid == 0) {
return getNamedParameterJdbcTemplate().query(
"SELECT message_id FROM messages WHERE " + (to != 0 ? " updated < :to AND" : StringUtils.EMPTY)
+ " NOT EXISTS (SELECT 1 from users u WHERE u.banned = 1"
+ " AND u.id = messages.user_id and u.id <> :uid) "
- + " ORDER BY updated DESC, message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + " ORDER BY updated DESC, message_id DESC " + limit(20),
sqlParameterSource, (rs, rowNum) -> rs.getInt(1));
}
return getNamedParameterJdbcTemplate().query(
@@ -626,7 +629,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "WHERE suser_id = :uid " + (to != 0 ? "AND updated < :to " : StringUtils.EMPTY)
+ " AND NOT EXISTS (SELECT 1 from users u WHERE u.banned = 1"
+ " AND u.id = messages.user_id and u.id <> :uid) "
- + "ORDER BY updated DESC, messages.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + "ORDER BY updated DESC, messages.message_id DESC " + limit(20),
sqlParameterSource, (rs, rowNum) -> rs.getInt(1));
}
@@ -639,7 +642,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
return getNamedParameterJdbcTemplate().queryForList("SELECT f.message_id FROM favorites f WHERE "
+ "EXISTS (SELECT 1 FROM subscr_users s WHERE s.suser_id = :uid and f.user_id = s.user_id)"
+ (before > 0 ? " AND f.message_id < :before " : StringUtils.EMPTY)
- + "ORDER BY f.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + "ORDER BY f.message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@@ -655,7 +658,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "(SELECT tag_id FROM messages_tags WHERE message_id = m.message_id) and :vid = bt.user_id)"
+ " AND NOT EXISTS (SELECT 1 from users u WHERE u.banned = 1 and u.id = m.user_id and u.id <> :vid) "
+ " AND NOT EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :vid and b.bl_user_id = m.user_id) "
- + " ORDER BY m.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + " ORDER BY m.message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@@ -673,16 +676,18 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
return getNamedParameterJdbcTemplate().queryForList(
"SELECT message_id FROM messages INNER JOIN users" + " ON messages.user_id = users.id"
+ " WHERE user_id = :uid" + (before > 0 ? " AND message_id < :before" : StringUtils.EMPTY)
- + " AND privacy >= :privacy AND users.banned = 0 ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + " AND privacy >= :privacy AND users.banned = 0 ORDER BY message_id DESC " + limit(20),
sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getUserTag(final int uid, final int tid, final int privacy, final int before) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("uid", uid).addValue("tid", tid)
- .addValue("privacy", privacy).addValue("before", before);
-
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("uid", uid)
+ .addValue("tid", tid)
+ .addValue("privacy", privacy)
+ .addValue("before", before);
return getNamedParameterJdbcTemplate()
.queryForList("SELECT messages.message_id FROM messages_tags INNER JOIN messages"
+ " ON messages.message_id = messages_tags.message_id" + " INNER JOIN users"
@@ -690,7 +695,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ " WHERE messages.user_id = :uid AND messages_tags.tag_id = :tid"
+ (before > 0 ? " AND messages.message_id < :before " : StringUtils.EMPTY)
+ " AND messages.privacy >= :privacy AND users.banned = 0"
- + " ORDER BY messages.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + " ORDER BY messages.message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@@ -709,7 +714,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
? " AND ts >= :date"
+ " AND ts < :date"
: StringUtils.EMPTY)
- + " AND privacy >= :privacy AND users.banned = 0 ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + " AND privacy >= :privacy AND users.banned = 0 ORDER BY message_id DESC " + limit(20),
sqlParameterSource, Integer.class);
}
@@ -731,7 +736,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ " AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN "
+ "(SELECT tag_id FROM messages_tags WHERE message_id = favorites.message_id) and :vid = bt.user_id)"
+ (before > 0 ? " AND messages.message_id < :before " : StringUtils.EMPTY)
- + " ORDER BY messages.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY) as r" + " UNION ALL "
+ + " ORDER BY messages.message_id DESC " + limit(20) + ") as r" + " UNION ALL "
+ "SELECT message_id FROM "
+ "(SELECT message_id FROM messages" + " INNER JOIN users" + " ON messages.user_id = users.id"
+ " WHERE user_id = :uid AND users.banned = 0"
@@ -739,33 +744,35 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ " AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN "
+ "(SELECT tag_id FROM messages_tags WHERE message_id = messages.message_id) and :vid = bt.user_id)"
+ (before > 0 ? " AND messages.message_id < :before" : StringUtils.EMPTY)
- + " AND privacy >= :privacy ORDER BY messages.message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY) as m "
- + "ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + " AND privacy >= :privacy ORDER BY messages.message_id DESC " + limit(20) + ") as m "
+ + "ORDER BY message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getUserRecommendations(final int uid, final int before) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("uid", uid).addValue("before",
- before);
-
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("uid", uid)
+ .addValue("before", before);
return getNamedParameterJdbcTemplate().queryForList("SELECT message_id FROM favorites" + " INNER JOIN users"
+ " ON favorites.user_id = users.id" + " WHERE user_id = :uid AND users.banned = 0 "
+ (before > 0 ? " AND message_id < :before " : StringUtils.EMPTY)
- + " ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY", sqlParameterSource, Integer.class);
+ + " ORDER BY message_id DESC " + limit(20), sqlParameterSource, Integer.class);
}
@Transactional(readOnly = true)
@Override
public List<Integer> getUserPhotos(final int uid, final int privacy, final int before) {
- SqlParameterSource sqlParameterSource = new MapSqlParameterSource().addValue("uid", uid)
- .addValue("privacy", privacy).addValue("before", before);
-
+ SqlParameterSource sqlParameterSource = new MapSqlParameterSource()
+ .addValue("uid", uid)
+ .addValue("privacy", privacy)
+ .addValue("before", before);
return getNamedParameterJdbcTemplate().queryForList(
"SELECT message_id FROM messages" + " INNER JOIN users" + " ON messages.user_id = users.id"
+ " WHERE user_id = :uid and users.banned = 0"
+ (before > 0 ? " AND message_id < :before " : StringUtils.EMPTY)
- + " AND privacy >= :privacy AND attach IS NOT NULL ORDER BY message_id DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY",
+ + " AND privacy >= :privacy AND attach IS NOT NULL ORDER BY message_id DESC "
+ + limit(20),
sqlParameterSource, Integer.class);
}
@@ -900,9 +907,6 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
.addValue("uid", user.getUid())
.addValue("now", Instant.now().atOffset(ZoneOffset.UTC), Types.TIMESTAMP_WITH_TIMEZONE),
new MessageMapper());
- if (replies.size() > 0 && !user.isAnonymous()) {
- setRead(user, mid);
- }
replies.forEach(i -> {
i.setEntities(MessageUtils.getEntities(i));
i.getUser().setAvatar(webApp.getAvatarUrl(i.getUser()));
@@ -1067,7 +1071,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
@Override
public void setLastReadComment(User user, Integer mid, Integer rid) {
jdbcTemplate.update(
- "UPDATE subscr_messages SET last_read_rid=GREATEST(?, last_read_rid) WHERE message_id=? AND suser_id=?",
+ "UPDATE subscr_messages SET last_read_rid=" + greatest() + "(?, last_read_rid) WHERE message_id=? AND suser_id=?",
rid, mid, user.getUid());
}
@@ -1102,9 +1106,12 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
if (message.isPresent()) {
Instant ts = message.get().getUpdatedAt();
if (ts.compareTo(messageEditingWindow) >= 0 || foreign) {
- return jdbcTemplate.update(
- "UPDATE messages_txt SET txt=?, updated_at=? WHERE messages_txt.message_id=?", body,
- now.atOffset(ZoneOffset.UTC), mid) > 0;
+ return namedParameterJdbcTemplate.update(
+ "UPDATE messages_txt SET txt=:txt, updated_at=:now WHERE messages_txt.message_id=:mid",
+ new MapSqlParameterSource()
+ .addValue("txt", body)
+ .addValue("mid", mid)
+ .addValue("now", now.atOffset(ZoneOffset.UTC), Types.TIMESTAMP_WITH_TIMEZONE)) > 0;
}
}
return false;
@@ -1113,9 +1120,13 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
if (reply != null) {
Instant ts = reply.getUpdatedAt();
if (ts.compareTo(messageEditingWindow) >= 0 || foreign) {
- return jdbcTemplate.update(
- "UPDATE replies SET txt=?, updated_at=? WHERE message_id=? AND reply_id=?", body,
- now.atOffset(ZoneOffset.UTC), mid, rid) > 0;
+ return namedParameterJdbcTemplate.update(
+ "UPDATE replies SET txt=:txt, updated_at=:now WHERE message_id=:mid AND reply_id=:rid",
+ new MapSqlParameterSource()
+ .addValue("txt", body)
+ .addValue("mid", mid)
+ .addValue("rid", rid)
+ .addValue("now", now.atOffset(ZoneOffset.UTC), Types.TIMESTAMP_WITH_TIMEZONE)) > 0;
}
}
return false;
diff --git a/src/main/java/com/juick/service/TagServiceImpl.java b/src/main/java/com/juick/service/TagServiceImpl.java
index 4657659b..c6f5bb89 100644
--- a/src/main/java/com/juick/service/TagServiceImpl.java
+++ b/src/main/java/com/juick/service/TagServiceImpl.java
@@ -18,8 +18,8 @@
package com.juick.service;
import com.juick.model.Tag;
-import com.juick.model.User;
import com.juick.model.TagStats;
+import com.juick.model.User;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -38,11 +38,7 @@ import java.sql.Statement;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -167,7 +163,8 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
public List<String> getPopularTags() {
return getJdbcTemplate().queryForList("""
select name from tags where noindex=0
- order by stat_messages desc OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY""", String.class);
+ order by stat_messages desc
+ """ + limit(20), String.class);
}
@Transactional(readOnly = true)
@@ -181,7 +178,8 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
INNER JOIN tags ON messages_tags.tag_id=tags.tag_id
WHERE tags.tag_id NOT IN (SELECT tag_id FROM tags_ignore)
GROUP BY tags.tag_id, tags.name HAVING COUNT(DISTINCT messages.user_id) > 1
- ORDER BY cnt DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY""";
+ ORDER BY cnt DESC
+ """ + limit(20);
return getNamedParameterJdbcTemplate()
.query(sql, new MapSqlParameterSource()
.addValue("ts", ts.atOffset(ZoneOffset.UTC), java.sql.Types.TIMESTAMP_WITH_TIMEZONE),
@@ -193,7 +191,6 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
public Set<Tag> updateTags(final int mid, final Collection<Tag> newTags) {
Set<Tag> currentTags = getMessageTags(mid).stream()
.map(TagStats::getTag).collect(Collectors.toSet());
-
if (CollectionUtils.isEmpty(newTags))
return currentTags;
@@ -240,7 +237,6 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
return Pair.of(body, tags);
}
- @Transactional(readOnly = true)
@Override
public List<TagStats> getMessageTags(final int mid) {
return getJdbcTemplate().query(
@@ -257,7 +253,6 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
}, mid);
}
- @Transactional(readOnly = true)
@Override
public List<Integer> getMessageTagsIDs(final int mid) {
return getJdbcTemplate().queryForList(
diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java
index 77fb2e72..e30be17b 100644
--- a/src/main/java/com/juick/service/UserServiceImpl.java
+++ b/src/main/java/com/juick/service/UserServiceImpl.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
@@ -38,6 +39,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.net.URI;
import java.sql.*;
import java.time.Instant;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;
@@ -46,15 +48,10 @@ import java.util.*;
*/
@Repository
public class UserServiceImpl extends BaseJdbcService implements UserService {
-
private static final Logger logger = LoggerFactory.getLogger("UserService");
-
@Value("${juick.admin_users:}")
List<String> adminUsers;
- @Value("#{new Boolean('${spring.sql.init.platform}' == 'sqlserver')}")
- private boolean omitRecursiveKeyword;
-
- private static class UserMapper implements RowMapper<User> {
+ private class UserMapper implements RowMapper<User> {
@Override
public User mapRow(@Nonnull ResultSet rs, int rowNum) throws SQLException {
User user = new User();
@@ -63,7 +60,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
user.setName(rs.getString(2));
user.setCredentials(rs.getString(3));
user.setBanned(rs.getInt(4) > 0);
- Timestamp seen = rs.getTimestamp(5);
+ OffsetDateTime seen = UserServiceImpl.this.getOffsetDateTime(rs, 5);
if (seen != null) {
user.setSeen(seen.toInstant());
}
@@ -105,7 +102,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
getJdbcTemplate().update("INSERT INTO subscr_users(user_id, suser_id) VALUES (2, ?)", uid);
return getUserByUID(uid);
}
- } catch (DataIntegrityViolationException e) {
+ } catch (DataIntegrityViolationException | UncategorizedSQLException e) {
throw new UsernameTakenException();
}
return Optional.empty();
@@ -446,15 +443,14 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
"SELECT users.id,users.nick FROM (subscr_users " +
"INNER JOIN users_subscr ON (subscr_users.suser_id=? " +
"AND subscr_users.user_id=users_subscr.user_id)) INNER JOIN users " +
- "ON subscr_users.user_id=users.id ORDER BY cnt OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY",
+ "ON subscr_users.user_id=users.id ORDER BY cnt " + limit(cnt),
(rs, num) -> {
User u = new User();
u.setUid(rs.getInt(1));
u.setName(rs.getString(2));
return u;
},
- uid,
- cnt);
+ uid);
}
@Transactional(readOnly = true)
@@ -593,6 +589,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
}
@Override
+ @Transactional
public void updateLastSeen(User user) {
getJdbcTemplate().update("UPDATE users SET last_seen=? WHERE id=?", Instant.now().atOffset(ZoneOffset.UTC), user.getUid());
}
diff --git a/src/main/java/com/juick/util/PrettyTimeFormatter.java b/src/main/java/com/juick/util/PrettyTimeFormatter.java
index 34a230f5..79240eb2 100644
--- a/src/main/java/com/juick/util/PrettyTimeFormatter.java
+++ b/src/main/java/com/juick/util/PrettyTimeFormatter.java
@@ -19,6 +19,7 @@ package com.juick.util;
import org.ocpsoft.prettytime.PrettyTime;
+import java.time.Instant;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Locale;
@@ -39,7 +40,7 @@ public class PrettyTimeFormatter {
}
};
- public String format(final Locale locale, final Date value)
+ public String format(final Locale locale, final Instant value)
{
PrettyTime prettyTime;
diff --git a/src/main/java/com/juick/www/api/Messages.java b/src/main/java/com/juick/www/api/Messages.java
index 06e3ac87..e3426454 100644
--- a/src/main/java/com/juick/www/api/Messages.java
+++ b/src/main/java/com/juick/www/api/Messages.java
@@ -174,6 +174,9 @@ public class Messages {
// update replies counter to exclude banned replies
msg.setReplies(replies.size());
if (!visitor.isAnonymous()) {
+ if (replies.size() > 0) {
+ messagesService.setRead(visitor, mid);
+ }
userService.updateLastSeen(visitor);
applicationEventPublisher
.publishEvent(new SystemEvent(this, SystemActivity.read(visitor, msg)));
diff --git a/src/main/java/com/juick/www/controllers/Site.java b/src/main/java/com/juick/www/controllers/Site.java
index eb483ccc..49736a60 100644
--- a/src/main/java/com/juick/www/controllers/Site.java
+++ b/src/main/java/com/juick/www/controllers/Site.java
@@ -582,6 +582,9 @@ public class Site {
&& (isReplyAuthor || !userService.isInBL(visitor.getUid(), reply.getUser().getUid())));
}
}
+ if (replies.size() > 0 && !visitor.isAnonymous()) {
+ messagesService.setRead(visitor, mid);
+ }
model.addAttribute("replies", replies);
return "views/thread";
}
diff --git a/src/main/java/com/mitchellbosecke/pebble/extension/FormatterExtension.java b/src/main/java/com/mitchellbosecke/pebble/extension/FormatterExtension.java
index bd8ba19d..4f6f3b53 100644
--- a/src/main/java/com/mitchellbosecke/pebble/extension/FormatterExtension.java
+++ b/src/main/java/com/mitchellbosecke/pebble/extension/FormatterExtension.java
@@ -34,7 +34,6 @@ public class FormatterExtension extends AbstractExtension {
Map<String, Filter> filters = new HashMap<>();
filters.put("formatMessage", new FormatMessageFilter());
filters.put("prettyTime", new PrettyTimeFilter());
- filters.put("timestamp", new TimestampFilter());
filters.put("tagsList", new TagsListFilter());
return filters;
}
diff --git a/src/main/java/com/mitchellbosecke/pebble/extension/filters/PrettyTimeFilter.java b/src/main/java/com/mitchellbosecke/pebble/extension/filters/PrettyTimeFilter.java
index f5d82bfe..e3708e2a 100644
--- a/src/main/java/com/mitchellbosecke/pebble/extension/filters/PrettyTimeFilter.java
+++ b/src/main/java/com/mitchellbosecke/pebble/extension/filters/PrettyTimeFilter.java
@@ -39,7 +39,7 @@ public class PrettyTimeFilter implements Filter {
public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {
if (input instanceof Instant) {
Locale locale = context.getLocale();
- return formatter.format(locale, Date.from((Instant)input));
+ return formatter.format(locale, (Instant)input);
}
throw new IllegalArgumentException("invalid input");
}
diff --git a/src/main/java/com/mitchellbosecke/pebble/extension/filters/TimestampFilter.java b/src/main/java/com/mitchellbosecke/pebble/extension/filters/TimestampFilter.java
deleted file mode 100644
index dfe93673..00000000
--- a/src/main/java/com/mitchellbosecke/pebble/extension/filters/TimestampFilter.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2008-2020, Juick
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.mitchellbosecke.pebble.extension.filters;
-
-import io.pebbletemplates.pebble.extension.Filter;
-import io.pebbletemplates.pebble.template.EvaluationContext;
-import io.pebbletemplates.pebble.template.PebbleTemplate;
-
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-public class TimestampFilter implements Filter {
- @Override
- public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {
- if (input instanceof Instant) {
- return Date.from((Instant)input);
- }
- throw new IllegalArgumentException("invalid input");
- }
-
- @Override
- public List<String> getArgumentNames() {
- return null;
- }
-}
diff --git a/src/main/resources/data-mysql.sql b/src/main/resources/data-mysql.sql
new file mode 100644
index 00000000..a5db173a
--- /dev/null
+++ b/src/main/resources/data-mysql.sql
@@ -0,0 +1,13 @@
+INSERT INTO users(id, nick, passw) VALUES(0, 'Anonymous', 'password');
+INSERT INTO users(id, nick, passw) VALUES(2, 'juick', 'password');
+INSERT INTO users(id, nick, passw) VALUES(5, 'archive', 'password');
+INSERT INTO tags(tag_id, name) VALUES(2, 'juick');
+ALTER TABLE tags AUTO_INCREMENT = 10;
+ALTER TABLE users AUTO_INCREMENT = 10;
+INSERT INTO reactions (like_id, description) VALUES (1, 'like');
+INSERT INTO reactions (like_id, description) VALUES (2, 'love');
+INSERT INTO reactions (like_id, description) VALUES (3, 'lol');
+INSERT INTO reactions (like_id, description) VALUES (4, 'hmm');
+INSERT INTO reactions (like_id, description) VALUES (5, 'angry');
+INSERT INTO reactions (like_id, description) VALUES (6, 'uhblya');
+INSERT INTO reactions (like_id, description) VALUES (7, 'ugh');
diff --git a/src/main/resources/data-sqlite.sql b/src/main/resources/data-sqlite.sql
index bb1203a4..d34cb832 100644
--- a/src/main/resources/data-sqlite.sql
+++ b/src/main/resources/data-sqlite.sql
@@ -1,11 +1,11 @@
-INSERT INTO users(id, nick, passw) VALUES(0, 'Anonymous', 'password');
-INSERT INTO users(id, nick, passw) VALUES(2, 'juick', 'password');
-INSERT INTO users(id, nick, passw) VALUES(5, 'archive', 'password');
-INSERT INTO tags(tag_id, name) VALUES(2, 'juick');
-INSERT INTO reactions (like_id, description) VALUES (1, 'like');
-INSERT INTO reactions (like_id, description) VALUES (2, 'love');
-INSERT INTO reactions (like_id, description) VALUES (3, 'lol');
-INSERT INTO reactions (like_id, description) VALUES (4, 'hmm');
-INSERT INTO reactions (like_id, description) VALUES (5, 'angry');
-INSERT INTO reactions (like_id, description) VALUES (6, 'uhblya');
-INSERT INTO reactions (like_id, description) VALUES (7, 'ugh');
+INSERT INTO users(id, nick, passw) VALUES(0, 'Anonymous', 'password');;
+INSERT INTO users(id, nick, passw) VALUES(2, 'juick', 'password');;
+INSERT INTO users(id, nick, passw) VALUES(5, 'archive', 'password');;
+INSERT INTO tags(tag_id, name) VALUES(2, 'juick');;
+INSERT INTO reactions (like_id, description) VALUES (1, 'like');;
+INSERT INTO reactions (like_id, description) VALUES (2, 'love');;
+INSERT INTO reactions (like_id, description) VALUES (3, 'lol');;
+INSERT INTO reactions (like_id, description) VALUES (4, 'hmm');;
+INSERT INTO reactions (like_id, description) VALUES (5, 'angry');;
+INSERT INTO reactions (like_id, description) VALUES (6, 'uhblya');;
+INSERT INTO reactions (like_id, description) VALUES (7, 'ugh') \ No newline at end of file
diff --git a/src/main/resources/schema-sqlite.sql b/src/main/resources/schema-sqlite.sql
index b624d67b..8e496069 100644
--- a/src/main/resources/schema-sqlite.sql
+++ b/src/main/resources/schema-sqlite.sql
@@ -1,64 +1,91 @@
-CREATE TABLE IF NOT EXISTS "user_services" (
+PRAGMA journal_mode=WAL;;
+CREATE TABLE user_services (
user_id INTEGER NOT NULL,
regid character varying(1024) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, service_type varchar(255) not null default 'fcm',
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ service_type varchar(255) not null default 'fcm',
FOREIGN KEY (user_id) REFERENCES users(id),
UNIQUE (regid)
-);
+);;
+CREATE TRIGGER INSERT_user_services AFTER INSERT ON user_services
+ BEGIN
+ UPDATE user_services SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE auth (
user_id INTEGER,
protocol TEXT CHECK (protocol IN ('xmpp', 'email', 'sms')) NOT NULL,
account character varying(128) NOT NULL,
authcode character varying(8) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_auth AFTER INSERT ON auth
+ BEGIN
+ UPDATE auth SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE bl_tags (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
tag_id bigint NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (tag_id) REFERENCES tags(tag_id)
-);
+);;
CREATE TABLE bl_users (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
bl_user_id bigint NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (user_id, bl_user_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (bl_user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_bl_users AFTER INSERT ON bl_users
+ BEGIN
+ UPDATE bl_users SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE emails (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
email character varying(128) NOT NULL,
subscr_hour smallint,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE facebook (
- user_id bigint,
+ user_id INTEGER,
fb_id numeric,
loginhash character varying(36),
access_token character varying(255),
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
fb_name character varying(64),
fb_link character varying(255) NOT NULL,
crosspost boolean DEFAULT true NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_facebook AFTER INSERT ON facebook
+ BEGIN
+ UPDATE facebook SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE favorites (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
message_id bigint NOT NULL,
ts timestamp with time zone,
like_id smallint DEFAULT 1 NOT NULL,
user_uri character varying(255) NOT NULL DEFAULT '',
FOREIGN KEY (like_id) REFERENCES reactions(like_id)
-);
+);;
CREATE TABLE followers (
- user_id bigint,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ user_id INTEGER,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
acct character varying(64) NOT NULL,
PRIMARY KEY (user_id)
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_followers AFTER INSERT ON followers
+ BEGIN
+ UPDATE followers SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE images (
mid bigint NOT NULL,
rid bigint NOT NULL,
@@ -68,33 +95,38 @@ CREATE TABLE images (
height bigint NOT NULL,
width bigint NOT NULL,
PRIMARY KEY (mid, rid)
-);
+);;
CREATE TABLE jids (
- user_id bigint,
+ user_id INTEGER,
jid character varying(64) NOT NULL,
active smallint DEFAULT 0 NOT NULL,
loginhash character varying(36),
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_jids AFTER INSERT ON jids
+ BEGIN
+ UPDATE jids SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE logins (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
hash character varying(16) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE meon (
id INTEGER NOT NULL,
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
link character varying(255) NOT NULL,
name character varying(32) NOT NULL,
ico smallint,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE messages (
message_id INTEGER PRIMARY KEY NOT NULL,
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
lang TEXT DEFAULT '__' NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
replies smallint DEFAULT (0) NOT NULL,
maxreplyid smallint DEFAULT (0) NOT NULL,
privacy smallint DEFAULT (1) NOT NULL,
@@ -106,35 +138,35 @@ CREATE TABLE messages (
popular smallint DEFAULT (0) NOT NULL,
hidden smallint DEFAULT (0) NOT NULL,
likes smallint DEFAULT (0) NOT NULL,
- updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ updated DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE messages_access (
message_id INTEGER NOT NULL,
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (message_id) REFERENCES messages(message_id)
-);
+);;
CREATE TABLE messages_properties (
- message_id INTEGER PRIMARY KEY NOT NULL,
+ message_id INTEGER NOT NULL,
reply_id smallint NOT NULL,
property_key character varying(255) NOT NULL,
property_value text NOT NULL,
UNIQUE (message_id, reply_id, property_key),
FOREIGN KEY (message_id) REFERENCES messages(message_id)
-);
+);;
CREATE TABLE messages_tags (
message_id INTEGER NOT NULL,
tag_id bigint NOT NULL,
FOREIGN KEY (message_id) REFERENCES messages(message_id)
-);
+);;
CREATE TABLE messages_txt (
message_id INTEGER NOT NULL,
repliesby text,
txt text NOT NULL,
- updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ updated_at DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (message_id) REFERENCES messages(message_id)
-);
+);;
CREATE TABLE places (
place_id INTEGER PRIMARY KEY NOT NULL,
lat numeric(10,7) NOT NULL,
@@ -143,60 +175,70 @@ CREATE TABLE places (
descr character varying(255),
url character varying(128),
user_id bigint NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
-);
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL
+);;
CREATE TABLE places_tags (
place_id INTEGER NOT NULL,
tag_id bigint NOT NULL,
FOREIGN KEY (place_id) REFERENCES places(place_id)
-);
+);;
CREATE TABLE pm (
- user_id bigint NOT NULL,
- user_id_to bigint NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ user_id INTEGER NOT NULL,
+ user_id_to INTEGER NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
txt text NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (user_id_to) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_pm AFTER INSERT ON pm
+ BEGIN
+ UPDATE pm SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE reactions (
like_id INTEGER PRIMARY KEY NOT NULL,
description character varying(100) NOT NULL
-);
+);;
CREATE TABLE replies (
message_id bigint NOT NULL,
reply_id smallint NOT NULL,
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
replyto smallint DEFAULT (0) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
attach TEXT CHECK (attach IN ('jpg', 'mp4', 'png')),
txt text NOT NULL,
- updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ updated_at DEFAULT CURRENT_TIMESTAMP NOT NULL,
user_uri character varying(255) DEFAULT NULL,
reply_uri character varying(255) DEFAULT NULL,
html smallint DEFAULT '0' NOT NULL,
FOREIGN KEY (message_id) REFERENCES messages(message_id),
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE subscr_messages (
message_id bigint NOT NULL,
- suser_id bigint NOT NULL,
+ suser_id INTEGER NOT NULL,
last_read_rid smallint DEFAULT 0 NOT NULL,
FOREIGN KEY (message_id) REFERENCES messages(message_id),
FOREIGN KEY (suser_id) REFERENCES users(id)
-);
+);;
CREATE TABLE subscr_tags (
tag_id bigint NOT NULL,
- suser_id bigint NOT NULL,
+ suser_id INTEGER NOT NULL,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id),
FOREIGN KEY (suser_id) REFERENCES users(id)
-);
+);;
CREATE TABLE subscr_users (
- user_id bigint NOT NULL,
- suser_id bigint NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ user_id INTEGER NOT NULL,
+ suser_id INTEGER NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (suser_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_subscr_users AFTER INSERT ON subscr_users
+ BEGIN
+ UPDATE subscr_users SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE tags (
tag_id INTEGER PRIMARY KEY NOT NULL,
synonym_id bigint,
@@ -206,46 +248,56 @@ CREATE TABLE tags (
stat_messages bigint DEFAULT (0) NOT NULL,
stat_users smallint DEFAULT (0) NOT NULL,
FOREIGN KEY (synonym_id) REFERENCES tags(tag_id)
-);
+);;
CREATE TABLE tags_ignore (
tag_id bigint NOT NULL,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id)
-);
+);;
CREATE TABLE tags_synonyms (
name character varying(64) NOT NULL,
changeto character varying(64) NOT NULL
-);
+);;
CREATE TABLE telegram (
- user_id bigint,
+ user_id INTEGER,
tg_id numeric NOT NULL,
tg_name character varying(64) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
loginhash character varying(36),
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_telegram AFTER INSERT ON telegram
+ BEGIN
+ UPDATE telegram SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE top_ignore_messages (
message_id bigint NOT NULL,
FOREIGN KEY (message_id) REFERENCES messages(message_id)
-);
+);;
CREATE TABLE top_ignore_tags (
tag_id NOT NULL,
FOREIGN KEY (tag_id) REFERENCES tag(tag_id)
-);
+);;
CREATE TABLE top_ignore_users (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE twitter (
user_id INTEGER NOT NULL,
access_token character varying(64) NOT NULL,
access_token_secret character varying(64) NOT NULL,
uname character varying(64) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
crosspost boolean DEFAULT true NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_twitter AFTER INSERT ON twitter
+ BEGIN
+ UPDATE twitter SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE users (
- id bigint NOT NULL,
+ id INTEGER PRIMARY KEY,
nick character varying(64) NOT NULL COLLATE NOCASE,
passw character varying(32) NOT NULL,
lang users_lang DEFAULT '__' NOT NULL,
@@ -255,15 +307,15 @@ CREATE TABLE users (
lastphoto bigint DEFAULT (0) NOT NULL,
karma smallint DEFAULT (0) NOT NULL,
last_seen timestamp with time zone,
- PRIMARY KEY (id)
-);
+ UNIQUE(nick)
+);;
CREATE TABLE users_subscr (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
cnt smallint DEFAULT (0) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE usersinfo (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
jid character varying(64),
fullname character varying(64),
country character varying(32),
@@ -271,26 +323,32 @@ CREATE TABLE usersinfo (
gender character varying(32),
bday character varying(32),
descr text,
+ UNIQUE(user_id),
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE vk (
- user_id bigint,
+ user_id INTEGER,
vk_id numeric NULL,
loginhash character varying(36),
access_token character varying(128) NOT NULL,
- ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ ts DEFAULT CURRENT_TIMESTAMP NOT NULL,
vk_name character varying(64) NOT NULL,
vk_link character varying(64) NOT NULL,
crosspost smallint DEFAULT (1) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-);
+);;
+CREATE TRIGGER INSERT_vk AFTER INSERT ON vk
+ BEGIN
+ UPDATE vk SET ts = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END;;
CREATE TABLE wl_users (
- user_id bigint NOT NULL,
+ user_id INTEGER NOT NULL,
wl_user_id bigint NOT NULL,
PRIMARY KEY (user_id, wl_user_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (wl_user_id) REFERENCES users(id)
-);
+);;
CREATE TABLE oauth2_registered_client (
id varchar(100) NOT NULL,
client_id varchar(100) NOT NULL,
@@ -305,4 +363,9 @@ CREATE TABLE oauth2_registered_client (
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
-);
+);;
+CREATE TRIGGER INSERT_oauth2_registered_client AFTER INSERT ON oauth2_registered_client
+ BEGIN
+ UPDATE oauth2_registered_client SET client_id_issued_at = strftime("%Y-%m-%dT%H:%M:%fZ", CURRENT_TIMESTAMP)
+ WHERE rowid = new.rowid;
+ END
diff --git a/src/main/resources/templates/views/partial/message.html b/src/main/resources/templates/views/partial/message.html
index 9a33c432..9d54d614 100644
--- a/src/main/resources/templates/views/partial/message.html
+++ b/src/main/resources/templates/views/partial/message.html
@@ -11,8 +11,8 @@
<i data-icon="ei-lock" data-size="s"></i> &middot;
{% endif %}
<a href="/{{ msg.user.name }}/{{ msg.mid }}">
- <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ <time datetime="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }}Z"
+ title="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }} GMT">
{{ msg.created | prettyTime }}
</time>
</a>
diff --git a/src/main/resources/templates/views/thread.html b/src/main/resources/templates/views/thread.html
index 7b68df66..ca74a2ff 100644
--- a/src/main/resources/templates/views/thread.html
+++ b/src/main/resources/templates/views/thread.html
@@ -16,8 +16,8 @@
<i data-icon="ei-lock" data-size="s"></i> &middot;
{% endif %}
<a href="/{{ msg.user.name }}/{{ msg.mid }}">
- <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ <time datetime="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }}Z"
+ title="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }} GMT">
{{ msg.created | prettyTime }}
</time>
</a>
@@ -143,8 +143,8 @@
{% endif %}
<div class="msg-ts">
<a href="/{{ msg.mid }}#{{ msg.rid }}">
- <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ <time datetime="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }}Z"
+ title="{{ msg.created | date('yyyy-MM-dd HH:mm:ss', timeZone="UTC") }} GMT">
{{ msg.created | prettyTime }}
</time>
</a>