forked from Qortal/qortal
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:
parent
32470fa641
commit
a9852e5305
@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
@ -24,6 +25,7 @@ import org.qortal.api.ApiErrors;
|
|||||||
import org.qortal.api.ApiExceptionFactory;
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
import org.qortal.api.Security;
|
import org.qortal.api.Security;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
|
import org.qortal.data.chat.ActiveChats;
|
||||||
import org.qortal.data.chat.ChatMessage;
|
import org.qortal.data.chat.ChatMessage;
|
||||||
import org.qortal.data.transaction.ChatTransactionData;
|
import org.qortal.data.transaction.ChatTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
@ -49,7 +51,7 @@ public class ChatResource {
|
|||||||
HttpServletRequest request;
|
HttpServletRequest request;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/search")
|
@Path("/messages")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Find chat messages",
|
summary = "Find chat messages",
|
||||||
description = "Returns CHAT messages that match criteria. Must provide EITHER 'txGroupId' OR two 'involving' addresses.",
|
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
|
@POST
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Build raw, unsigned, CHAT transaction",
|
summary = "Build raw, unsigned, CHAT transaction",
|
||||||
|
95
src/main/java/org/qortal/data/chat/ActiveChats.java
Normal file
95
src/main/java/org/qortal/data/chat/ActiveChats.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,15 +17,9 @@ public class ChatMessage {
|
|||||||
/* Address of sender */
|
/* Address of sender */
|
||||||
private String sender;
|
private String sender;
|
||||||
|
|
||||||
/* Registered name of sender (if any) */
|
|
||||||
private String senderName; // can be null
|
|
||||||
|
|
||||||
/* Address of recipient (if any) */
|
/* Address of recipient (if any) */
|
||||||
private String recipient; // can be null
|
private String recipient; // can be null
|
||||||
|
|
||||||
/* Registered name of recipient (if any) */
|
|
||||||
private String recipientName; // can be null
|
|
||||||
|
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
|
|
||||||
private boolean isText;
|
private boolean isText;
|
||||||
@ -38,15 +32,13 @@ public class ChatMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For repository use
|
// For repository use
|
||||||
public ChatMessage(long timestamp, int txGroupId, byte[] senderPublicKey, String sender, String senderName,
|
public ChatMessage(long timestamp, int txGroupId, byte[] senderPublicKey, String sender,
|
||||||
String recipient, String recipientName, byte[] data, boolean isText, boolean isEncrypted) {
|
String recipient, byte[] data, boolean isText, boolean isEncrypted) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.txGroupId = txGroupId;
|
this.txGroupId = txGroupId;
|
||||||
this.senderPublicKey = senderPublicKey;
|
this.senderPublicKey = senderPublicKey;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.senderName = senderName;
|
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.recipientName = recipientName;
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.isText = isText;
|
this.isText = isText;
|
||||||
this.isEncrypted = isEncrypted;
|
this.isEncrypted = isEncrypted;
|
||||||
@ -68,18 +60,10 @@ public class ChatMessage {
|
|||||||
return this.sender;
|
return this.sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSenderName() {
|
|
||||||
return this.senderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRecipient() {
|
public String getRecipient() {
|
||||||
return this.recipient;
|
return this.recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRecipientName() {
|
|
||||||
return this.recipientName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
public byte[] getData() {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.qortal.repository;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qortal.data.chat.ActiveChats;
|
||||||
import org.qortal.data.chat.ChatMessage;
|
import org.qortal.data.chat.ChatMessage;
|
||||||
|
|
||||||
public interface ChatRepository {
|
public interface ChatRepository {
|
||||||
@ -15,4 +16,6 @@ public interface ChatRepository {
|
|||||||
Integer txGroupId, List<String> involving,
|
Integer txGroupId, List<String> involving,
|
||||||
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public ActiveChats getActiveChats(String address) throws DataException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,13 @@ import java.sql.SQLException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.data.chat.ChatMessage;
|
||||||
import org.qortal.repository.ChatRepository;
|
import org.qortal.repository.ChatRepository;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
public class HSQLDBChatRepository implements ChatRepository {
|
public class HSQLDBChatRepository implements ChatRepository {
|
||||||
|
|
||||||
@ -28,12 +32,9 @@ public class HSQLDBChatRepository implements ChatRepository {
|
|||||||
|
|
||||||
StringBuilder sql = new StringBuilder(1024);
|
StringBuilder sql = new StringBuilder(1024);
|
||||||
|
|
||||||
sql.append("SELECT created_when, tx_group_id, creator, sender, SenderNames.name, "
|
sql.append("SELECT created_when, tx_group_id, creator, sender, recipient, data, is_text, is_encrypted "
|
||||||
+ "recipient, RecipientNames.name, data, is_text, is_encrypted "
|
|
||||||
+ "FROM ChatTransactions "
|
+ "FROM ChatTransactions "
|
||||||
+ "JOIN Transactions USING (signature) "
|
+ "JOIN Transactions USING (signature) ");
|
||||||
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
|
|
||||||
+ "LEFT OUTER JOIN Names AS RecipientNames ON RecipientNames.owner = recipient ");
|
|
||||||
|
|
||||||
// WHERE clauses
|
// WHERE clauses
|
||||||
|
|
||||||
@ -88,15 +89,13 @@ public class HSQLDBChatRepository implements ChatRepository {
|
|||||||
int groupId = resultSet.getInt(2);
|
int groupId = resultSet.getInt(2);
|
||||||
byte[] senderPublicKey = resultSet.getBytes(3);
|
byte[] senderPublicKey = resultSet.getBytes(3);
|
||||||
String sender = resultSet.getString(4);
|
String sender = resultSet.getString(4);
|
||||||
String senderName = resultSet.getString(5);
|
String recipient = resultSet.getString(5);
|
||||||
String recipient = resultSet.getString(6);
|
byte[] data = resultSet.getBytes(6);
|
||||||
String recipientName = resultSet.getString(7);
|
boolean isText = resultSet.getBoolean(7);
|
||||||
byte[] data = resultSet.getBytes(8);
|
boolean isEncrypted = resultSet.getBoolean(8);
|
||||||
boolean isText = resultSet.getBoolean(9);
|
|
||||||
boolean isEncrypted = resultSet.getBoolean(10);
|
|
||||||
|
|
||||||
ChatMessage chatMessage = new ChatMessage(timestamp, groupId, senderPublicKey, sender,
|
ChatMessage chatMessage = new ChatMessage(timestamp, groupId, senderPublicKey, sender,
|
||||||
senderName, recipient, recipientName, data, isText, isEncrypted);
|
recipient, data, isText, isEncrypted);
|
||||||
|
|
||||||
chatMessages.add(chatMessage);
|
chatMessages.add(chatMessage);
|
||||||
} while (resultSet.next());
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user