More work on CHAT API / support.

Renamed GET /chats/search to /chats/messages.

Added GET /chats/active/{address} to return lists of group chats
and direct chats involving {address}, where a chat message exists.
This commit is contained in:
catbref 2020-05-12 14:28:41 +01:00
parent 32470fa641
commit a9852e5305
5 changed files with 226 additions and 31 deletions

View File

@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@ -24,6 +25,7 @@ import org.qortal.api.ApiErrors;
import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.Security;
import org.qortal.crypto.Crypto;
import org.qortal.data.chat.ActiveChats;
import org.qortal.data.chat.ChatMessage;
import org.qortal.data.transaction.ChatTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -49,7 +51,7 @@ public class ChatResource {
HttpServletRequest request;
@GET
@Path("/search")
@Path("/messages")
@Operation(
summary = "Find chat messages",
description = "Returns CHAT messages that match criteria. Must provide EITHER 'txGroupId' OR two 'involving' addresses.",
@ -100,6 +102,34 @@ public class ChatResource {
}
}
@GET
@Path("/active/{address}")
@Operation(
summary = "Find active chats (group/direct) involving address",
responses = {
@ApiResponse(
content = @Content(
array = @ArraySchema(
schema = @Schema(
implementation = ActiveChats.class
)
)
)
)
}
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public ActiveChats getActiveChats(@PathParam("address") String address) {
if (address == null || !Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
return repository.getChatRepository().getActiveChats(address);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@POST
@Operation(
summary = "Build raw, unsigned, CHAT transaction",

View File

@ -0,0 +1,95 @@
package org.qortal.data.chat;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class ActiveChats {
@XmlAccessorType(XmlAccessType.FIELD)
public static class GroupChat {
private int groupId;
private String groupName;
private long timestamp;
protected GroupChat() {
/* JAXB */
}
public GroupChat(int groupId, String groupName, long timestamp) {
this.groupId = groupId;
this.groupName = groupName;
this.timestamp = timestamp;
}
public int getGroupId() {
return this.groupId;
}
public String getGroupName() {
return this.groupName;
}
public long getTimestamp() {
return this.timestamp;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class DirectChat {
private String address;
private String name;
private long timestamp;
protected DirectChat() {
/* JAXB */
}
public DirectChat(String address, String name, long timestamp) {
this.address = address;
this.name = name;
this.timestamp = timestamp;
}
public String getAddress() {
return this.address;
}
public String getName() {
return this.name;
}
public long getTimestamp() {
return this.timestamp;
}
}
// Properties
private List<GroupChat> groups;
private List<DirectChat> direct;
// Constructors
protected ActiveChats() {
/* For JAXB */
}
// For repository use
public ActiveChats(List<GroupChat> groups, List<DirectChat> direct) {
this.groups = groups;
this.direct = direct;
}
public List<GroupChat> getGroups() {
return this.groups;
}
public List<DirectChat> getDirect() {
return this.direct;
}
}

View File

@ -17,15 +17,9 @@ public class ChatMessage {
/* Address of sender */
private String sender;
/* Registered name of sender (if any) */
private String senderName; // can be null
/* Address of recipient (if any) */
private String recipient; // can be null
/* Registered name of recipient (if any) */
private String recipientName; // can be null
private byte[] data;
private boolean isText;
@ -38,15 +32,13 @@ public class ChatMessage {
}
// For repository use
public ChatMessage(long timestamp, int txGroupId, byte[] senderPublicKey, String sender, String senderName,
String recipient, String recipientName, byte[] data, boolean isText, boolean isEncrypted) {
public ChatMessage(long timestamp, int txGroupId, byte[] senderPublicKey, String sender,
String recipient, byte[] data, boolean isText, boolean isEncrypted) {
this.timestamp = timestamp;
this.txGroupId = txGroupId;
this.senderPublicKey = senderPublicKey;
this.sender = sender;
this.senderName = senderName;
this.recipient = recipient;
this.recipientName = recipientName;
this.data = data;
this.isText = isText;
this.isEncrypted = isEncrypted;
@ -68,18 +60,10 @@ public class ChatMessage {
return this.sender;
}
public String getSenderName() {
return this.senderName;
}
public String getRecipient() {
return this.recipient;
}
public String getRecipientName() {
return this.recipientName;
}
public byte[] getData() {
return this.data;
}

View File

@ -2,6 +2,7 @@ package org.qortal.repository;
import java.util.List;
import org.qortal.data.chat.ActiveChats;
import org.qortal.data.chat.ChatMessage;
public interface ChatRepository {
@ -15,4 +16,6 @@ public interface ChatRepository {
Integer txGroupId, List<String> involving,
Integer limit, Integer offset, Boolean reverse) throws DataException;
public ActiveChats getActiveChats(String address) throws DataException;
}

View File

@ -5,9 +5,13 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.qortal.data.chat.ActiveChats;
import org.qortal.data.chat.ActiveChats.DirectChat;
import org.qortal.data.chat.ActiveChats.GroupChat;
import org.qortal.data.chat.ChatMessage;
import org.qortal.repository.ChatRepository;
import org.qortal.repository.DataException;
import org.qortal.transaction.Transaction.TransactionType;
public class HSQLDBChatRepository implements ChatRepository {
@ -28,12 +32,9 @@ public class HSQLDBChatRepository implements ChatRepository {
StringBuilder sql = new StringBuilder(1024);
sql.append("SELECT created_when, tx_group_id, creator, sender, SenderNames.name, "
+ "recipient, RecipientNames.name, data, is_text, is_encrypted "
sql.append("SELECT created_when, tx_group_id, creator, sender, recipient, data, is_text, is_encrypted "
+ "FROM ChatTransactions "
+ "JOIN Transactions USING (signature) "
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
+ "LEFT OUTER JOIN Names AS RecipientNames ON RecipientNames.owner = recipient ");
+ "JOIN Transactions USING (signature) ");
// WHERE clauses
@ -88,15 +89,13 @@ public class HSQLDBChatRepository implements ChatRepository {
int groupId = resultSet.getInt(2);
byte[] senderPublicKey = resultSet.getBytes(3);
String sender = resultSet.getString(4);
String senderName = resultSet.getString(5);
String recipient = resultSet.getString(6);
String recipientName = resultSet.getString(7);
byte[] data = resultSet.getBytes(8);
boolean isText = resultSet.getBoolean(9);
boolean isEncrypted = resultSet.getBoolean(10);
String recipient = resultSet.getString(5);
byte[] data = resultSet.getBytes(6);
boolean isText = resultSet.getBoolean(7);
boolean isEncrypted = resultSet.getBoolean(8);
ChatMessage chatMessage = new ChatMessage(timestamp, groupId, senderPublicKey, sender,
senderName, recipient, recipientName, data, isText, isEncrypted);
recipient, data, isText, isEncrypted);
chatMessages.add(chatMessage);
} while (resultSet.next());
@ -107,4 +106,88 @@ public class HSQLDBChatRepository implements ChatRepository {
}
}
@Override
public ActiveChats getActiveChats(String address) throws DataException {
List<GroupChat> groupChats = getActiveGroupChats(address);
List<DirectChat> directChats = getActiveDirectChats(address);
return new ActiveChats(groupChats, directChats);
}
private List<GroupChat> getActiveGroupChats(String address) throws DataException {
// Find groups where address is a member and there is a chat
String groupsSql = "SELECT group_id, group_name, latest_timestamp "
+ "FROM GroupMembers "
+ "JOIN Groups USING (group_id) "
+ "CROSS JOIN LATERAL("
+ "SELECT created_when "
+ "FROM Transactions "
+ "WHERE tx_group_id = Groups.group_id AND type = " + TransactionType.CHAT.value + " "
+ "ORDER BY created_when DESC "
+ "LIMIT 1"
+ ") AS LatestMessages (latest_timestamp) "
+ "WHERE address = ?";
List<GroupChat> groupChats = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(groupsSql, address)) {
if (resultSet == null)
return groupChats;
do {
int groupId = resultSet.getInt(1);
String groupName = resultSet.getString(2);
long timestamp = resultSet.getLong(3);
GroupChat groupChat = new GroupChat(groupId, groupName, timestamp);
groupChats.add(groupChat);
} while (resultSet.next());
} catch (SQLException e) {
throw new DataException("Unable to fetch active group chats from repository", e);
}
return groupChats;
}
private List<DirectChat> getActiveDirectChats(String address) throws DataException {
// Find chat messages involving address
String directSql = "SELECT other_address, name, latest_timestamp "
+ "FROM ("
+ "SELECT recipient FROM ChatTransactions "
+ "WHERE sender = ? AND recipient IS NOT NULL "
+ "UNION "
+ "SELECT sender FROM ChatTransactions "
+ "WHERE recipient = ?"
+ ") AS OtherParties (other_address) "
+ "CROSS JOIN LATERAL("
+ "SELECT created_when FROM ChatTransactions "
+ "NATURAL JOIN Transactions "
+ "WHERE (sender = other_address AND recipient = ?) "
+ "OR (sender = ? AND recipient = other_address) "
+ "ORDER BY created_when DESC "
+ "LIMIT 1"
+ ") AS LatestMessages (latest_timestamp) "
+ "LEFT OUTER JOIN Names ON owner = other_address";
Object[] bindParams = new Object[] { address, address, address, address };
List<DirectChat> directChats = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(directSql, bindParams)) {
if (resultSet == null)
return directChats;
do {
String otherAddress = resultSet.getString(1);
String name = resultSet.getString(2);
long timestamp = resultSet.getLong(3);
DirectChat directChat = new DirectChat(otherAddress, name, timestamp);
directChats.add(directChat);
} while (resultSet.next());
} catch (SQLException e) {
throw new DataException("Unable to fetch active direct chats from repository", e);
}
return directChats;
}
}