Messages/Chats CRUD

This commit is contained in:
Vitaliy Vlasov 2020-11-19 13:10:09 +02:00 committed by Michael Bradley
parent 519ba09d67
commit 121a29a086
6 changed files with 546 additions and 0 deletions

View File

@ -80,3 +80,5 @@ task tests, "Run all tests":
buildAndRunTest "contacts"
buildAndRunTest "account"
buildAndRunTest "tokens"
buildAndRunTest "messages"
buildAndRunTest "chats"

View File

@ -3,6 +3,8 @@ import lib/alias
import lib/alias/data
import lib/account
import lib/identicon
import lib/messages
import lib/chats
import lib/util
import nimcrypto
import strformat

117
nim_status/lib/chats.nim Normal file
View File

@ -0,0 +1,117 @@
import # nim libs
json, options, strutils, strformat
import # vendor libs
web3/conversions as web3_conversions, web3/ethtypes,
sqlcipher, json_serialization, json_serialization/[reader, writer, lexer],
stew/byteutils
type
ChatType* {.pure.} = enum
Id = "id",
Name = "name",
Color = "color",
ChatType = "chatType",
Active = "active",
Timestamp = "timestamp",
DeletedAtClockValue = "deletedAtClockValue",
PublicKey = "publicKey",
UnviewedMessageCount = "unviewedMessageCount",
LastClockValue = "lastClockValue",
LastMessage = "lastMessage",
Members = "members",
MembershipUpdates = "membershipUpdates"
Profile = "profile",
InvitationAdmin = "invitationAdmin",
Muted = "muted"
ChatCol* {.pure.} = enum
Id = "id",
Name = "name",
Color = "color",
ChatType = "type",
Active = "active",
Timestamp = "timestamp",
DeletedAtClockValue = "deleted_at_clock_value",
PublicKey = "public_key",
UnviewedMessageCount = "unviewed_message_count",
LastClockValue = "last_clock_value",
LastMessage = "last_message",
Members = "members",
MembershipUpdates = "membership_updates"
Profile = "profile",
InvitationAdmin = "invitation_admin",
Muted = "muted"
Chat* = object
id* {.serializedFieldName($ChatType.Id), dbColumnName($ChatCol.Id).}: string
name* {.serializedFieldName($ChatType.Name), dbColumnName($ChatCol.Name).}: string
color* {.serializedFieldName($ChatType.Color), dbColumnName($ChatCol.Color).}: string
chatType* {.serializedFieldName($ChatType.ChatType), dbColumnName($ChatCol.ChatType).}: int
active* {.serializedFieldName($ChatType.Active), dbColumnName($ChatCol.Active).}: bool
timestamp* {.serializedFieldName($ChatType.Timestamp), dbColumnName($ChatCol.Timestamp).}: int
deletedAtClockValue* {.serializedFieldName($ChatType.DeletedAtClockValue), dbColumnName($ChatCol.DeletedAtClockValue).}: int
publicKey* {.serializedFieldName($ChatType.PublicKey), dbColumnName($ChatCol.PublicKey).}: seq[byte]
unviewedMessageCount* {.serializedFieldName($ChatType.UnviewedMessageCount), dbColumnName($ChatCol.UnviewedMessageCount).}: int
lastClockValue* {.serializedFieldName($ChatType.LastClockValue), dbColumnName($ChatCol.LastClockValue).}: int
lastMessage* {.serializedFieldName($ChatType.LastMessage), dbColumnName($ChatCol.LastMessage).}: seq[byte]
members* {.serializedFieldName($ChatType.Members), dbColumnName($ChatCol.Members).}: seq[byte]
membershipUpdates* {.serializedFieldName($ChatType.MembershipUpdates), dbColumnName($ChatCol.MembershipUpdates).}: seq[byte]
profile* {.serializedFieldName($ChatType.Profile), dbColumnName($ChatCol.Profile).}: string
invitationAdmin* {.serializedFieldName($ChatType.InvitationAdmin), dbColumnName($ChatCol.InvitationAdmin).}: string
muted* {.serializedFieldName($ChatType.Muted), dbColumnName($ChatCol.Muted).}: bool
proc getChats*(db: DbConn): seq[Chat] =
let query = """SELECT * from chats"""
result = db.all(Chat, query)
proc getChatById*(db: DbConn, id: string): Option[Chat] =
let query = """SELECT * from chats where id = ?"""
result = db.one(Chat, query, id)
proc saveChat*(db: DbConn, chat: Chat) =
let query = fmt"""INSERT INTO chats(
{$ChatCol.Id},
{$ChatCol.Name},
{$ChatCol.Color},
{$ChatCol.ChatType},
{$ChatCol.Active},
{$ChatCol.Timestamp},
{$ChatCol.DeletedAtClockValue},
{$ChatCol.PublicKey},
{$ChatCol.UnviewedMessageCount},
{$ChatCol.LastClockValue},
{$ChatCol.LastMessage},
{$ChatCol.Members},
{$ChatCol.MembershipUpdates},
{$ChatCol.Profile},
{$ChatCol.InvitationAdmin},
{$ChatCol.Muted})
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
"""
db.exec(query,
chat.id,
chat.name,
chat.color,
chat.chatType,
chat.active,
chat.timestamp,
chat.deletedAtClockValue,
chat.publicKey,
chat.unviewedMessageCount,
chat.lastClockValue,
chat.lastMessage,
chat.members,
chat.membershipUpdates,
chat.profile,
chat.invitationAdmin,
chat.muted)
proc deleteChat*(db: DbConn, chat: Chat) =
let query = fmt"""DELETE FROM chats where id = ?"""
db.exec(query, chat.id)

259
nim_status/lib/messages.nim Normal file
View File

@ -0,0 +1,259 @@
import # nim libs
json, options, sequtils, strutils, strformat
import # vendor libs
web3/conversions as web3_conversions, web3/ethtypes,
sqlcipher, json_serialization, json_serialization/[reader, writer, lexer],
stew/byteutils
import conversions, settings/types
type
MessageType* {.pure.} = enum
Id = "id",
WhisperTimestamp = "whisperTimestamp ",
Source = "source",
Destination = "destination",
Text = "text",
ContentType = "contentType",
Username = "username",
Timestamp = "timestamp",
ChatId = "chatId",
LocalChatId = "localChatId",
Hide = "hide",
ResponseTo = "responseTo",
MessageType = "messageType",
ClockValue = "clockValue",
Seen = "seen",
OutgoingStatus = "outgoingStatus",
ParsedText = "parsedText",
RawPayload = "rawPayload",
StickerPack = "stickerPack",
StickerHash = "stickerHash",
CommandId = "commandId",
CommandValue = "commandValue",
CommandAddress = "commandAddress",
CommandFrom = "commandFrom",
CommandContract = "commandContract",
CommandTransactionHash = "commandTransactionHash",
CommandSignature = "commandSignature",
CommandState = "commandState",
AudioPayload = "audioPayload",
AudioType = "audioType",
AudioDurationMs = "audioDurationMs",
AudioBase64 = "audioBase64",
ReplaceMessage = "replaceMessage",
Rtl = "rtl",
LineCount = "lineCount",
Links = "links",
Mentions = "mentions",
ImagePayload = "imagePayload",
ImageType = "imageType",
ImageBase64 = "imageBase64"
MessageCol* {.pure.} = enum
Id = "id",
WhisperTimestamp = "whisper_timestamp",
Source = "source",
Destination = "destination",
Text = "text",
ContentType = "content_type",
Username = "username",
Timestamp = "timestamp",
ChatId = "chat_id",
LocalChatId = "local_chat_id",
Hide = "hide",
ResponseTo = "response_to",
MessageType = "message_type",
ClockValue = "clock_value",
Seen = "seen",
OutgoingStatus = "outgoing_status",
ParsedText = "parsed_text",
RawPayload = "raw_payload",
StickerPack = "sticker_pack",
StickerHash = "sticker_hash",
CommandId = "command_id",
CommandValue = "command_value",
CommandAddress = "command_address",
CommandFrom = "command_from",
CommandContract = "command_contract",
CommandTransactionHash = "command_transaction_hash",
CommandSignature = "command_signature",
CommandState = "command_state",
AudioPayload = "audio_payload",
AudioType = "audio_type",
AudioDurationMs = "audio_duration_ms",
AudioBase64 = "audio_base64",
ReplaceMessage = "replace_message",
Rtl = "rtl",
LineCount = "line_count",
Links = "links",
Mentions = "mentions",
ImagePayload = "image_payload",
ImageType = "image_type",
ImageBase64 = "image_base64"
Message* = object
id* {.serializedFieldName($MessageType.Id), dbColumnName($MessageCol.Id).}: string
whisperTimestamp* {.serializedFieldName($MessageType.WhisperTimestamp), dbColumnName($MessageCol.WhisperTimestamp).}: int
source* {.serializedFieldName($MessageType.Source), dbColumnName($MessageCol.Source).}: string
destination* {.serializedFieldName($MessageType.Destination), dbColumnName($MessageCol.Destination).}: seq[byte]
text* {.serializedFieldName($MessageType.Text), dbColumnName($MessageCol.Text).}: string
contentType* {.serializedFieldName($MessageType.ContentType), dbColumnName($MessageCol.ContentType).}: int
username* {.serializedFieldName($MessageType.Username), dbColumnName($MessageCol.Username).}: string
timestamp* {.serializedFieldName($MessageType.Timestamp), dbColumnName($MessageCol.Timestamp).}: int
chatId* {.serializedFieldName($MessageType.ChatId), dbColumnName($MessageCol.ChatId).}: string
localChatId* {.serializedFieldName($MessageType.LocalChatId), dbColumnName($MessageCol.LocalChatId).}: string
hide* {.serializedFieldName($MessageType.Hide), dbColumnName($MessageCol.Hide).}: bool
responseTo* {.serializedFieldName($MessageType.ResponseTo), dbColumnName($MessageCol.ResponseTo).}: string
messageType* {.serializedFieldName($MessageType.MessageType), dbColumnName($MessageCol.MessageType).}: int
clockValue* {.serializedFieldName($MessageType.ClockValue), dbColumnName($MessageCol.ClockValue).}: int
seen* {.serializedFieldName($MessageType.Seen), dbColumnName($MessageCol.Seen).}: bool
outgoingStatus* {.serializedFieldName($MessageType.OutgoingStatus), dbColumnName($MessageCol.OutgoingStatus).}: string
parsedText* {.serializedFieldName($MessageType.ParsedText), dbColumnName($MessageCol.ParsedText).}: seq[byte]
rawPayload* {.serializedFieldName($MessageType.RawPayload), dbColumnName($MessageCol.RawPayload).}: seq[byte]
stickerPack* {.serializedFieldName($MessageType.StickerPack), dbColumnName($MessageCol.StickerPack).}: int
stickerHash* {.serializedFieldName($MessageType.StickerHash), dbColumnName($MessageCol.StickerHash).}: string
commandId* {.serializedFieldName($MessageType.CommandId), dbColumnName($MessageCol.CommandId).}: string
commandValue* {.serializedFieldName($MessageType.CommandValue), dbColumnName($MessageCol.CommandValue).}: string
commandAddress* {.serializedFieldName($MessageType.CommandAddress), dbColumnName($MessageCol.CommandAddress).}: string
commandFrom* {.serializedFieldName($MessageType.CommandFrom), dbColumnName($MessageCol.CommandFrom).}: string
commandContract* {.serializedFieldName($MessageType.CommandContract), dbColumnName($MessageCol.CommandContract).}: string
commandTransactionHash* {.serializedFieldName($MessageType.CommandTransactionHash), dbColumnName($MessageCol.CommandTransactionHash).}: string
commandSignature* {.serializedFieldName($MessageType.CommandSignature), dbColumnName($MessageCol.CommandSignature).}: seq[byte]
commandState* {.serializedFieldName($MessageType.CommandState), dbColumnName($MessageCol.CommandState).}: int
audioPayload* {.serializedFieldName($MessageType.AudioPayload), dbColumnName($MessageCol.AudioPayload).}: seq[byte]
audioType* {.serializedFieldName($MessageType.AudioType), dbColumnName($MessageCol.AudioType).}: int
audioDurationMs* {.serializedFieldName($MessageType.AudioDurationMs), dbColumnName($MessageCol.AudioDurationMs).}: int
audioBase64* {.serializedFieldName($MessageType.AudioBase64), dbColumnName($MessageCol.AudioBase64).}: string
replaceMessage* {.serializedFieldName($MessageType.ReplaceMessage), dbColumnName($MessageCol.ReplaceMessage).}: string
rtl* {.serializedFieldName($MessageType.Rtl), dbColumnName($MessageCol.Rtl).}: bool
lineCount* {.serializedFieldName($MessageType.LineCount), dbColumnName($MessageCol.LineCount).}: int
links* {.serializedFieldName($MessageType.Links), dbColumnName($MessageCol.Links).}: string
mentions* {.serializedFieldName($MessageType.Mentions), dbColumnName($MessageCol.Mentions).}: string
imagePayload* {.serializedFieldName($MessageType.ImagePayload), dbColumnName($MessageCol.ImagePayload).}: seq[byte]
imageType* {.serializedFieldName($MessageType.ImageType), dbColumnName($MessageCol.ImageType).}: string
imageBase64* {.serializedFieldName($MessageType.ImageBase64), dbColumnName($MessageCol.ImageBase64).}: string
proc getMessageById*(db: DbConn, id: string): Option[Message] =
let query = """SELECT * from user_messages where id = ?"""
result = db.one(Message, query, id)
proc saveMessage*(db: DbConn, message: Message) =
let query = fmt"""INSERT INTO user_messages(
{$MessageCol.Id},
{$MessageCol.WhisperTimestamp},
{$MessageCol.Source},
{$MessageCol.Destination},
{$MessageCol.Text},
{$MessageCol.ContentType},
{$MessageCol.Username},
{$MessageCol.Timestamp},
{$MessageCol.ChatId},
{$MessageCol.LocalChatId},
{$MessageCol.Hide},
{$MessageCol.ResponseTo},
{$MessageCol.MessageType},
{$MessageCol.ClockValue},
{$MessageCol.Seen},
{$MessageCol.OutgoingStatus},
{$MessageCol.ParsedText},
{$MessageCol.RawPayload},
{$MessageCol.StickerPack},
{$MessageCol.StickerHash},
{$MessageCol.CommandId},
{$MessageCol.CommandValue},
{$MessageCol.CommandAddress},
{$MessageCol.CommandFrom},
{$MessageCol.CommandContract},
{$MessageCol.CommandTransactionHash},
{$MessageCol.CommandSignature},
{$MessageCol.CommandState},
{$MessageCol.AudioPayload},
{$MessageCol.AudioType},
{$MessageCol.AudioDurationMs},
{$MessageCol.AudioBase64},
{$MessageCol.ReplaceMessage},
{$MessageCol.Rtl},
{$MessageCol.LineCount},
{$MessageCol.Links},
{$MessageCol.Mentions},
{$MessageCol.ImagePayload},
{$MessageCol.ImageType},
{$MessageCol.ImageBase64})
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
db.exec(query,
message.id,
message.whisperTimestamp,
message.source,
message.destination,
message.text,
message.contentType,
message.username,
message.timestamp,
message.chatId,
message.localChatId,
message.hide,
message.responseTo,
message.messageType,
message.clockValue,
message.seen,
message.outgoingStatus,
message.parsedText,
message.rawPayload,
message.stickerPack,
message.stickerHash,
message.commandId,
message.commandValue,
message.commandAddress,
message.commandFrom,
message.commandContract,
message.commandTransactionHash,
message.commandSignature,
message.commandState,
message.audioPayload,
message.audioType,
message.audioDurationMs,
message.audioBase64,
message.replaceMessage,
message.rtl,
message.lineCount,
message.links,
message.mentions,
message.imagePayload,
message.imageType,
message.imageBase64)
proc deleteMessage*(db: DbConn, message: Message) =
let query = fmt"""
DELETE FROM user_messages where id = ?"""
db.exec(query, message.id)
proc markAllRead*(db: DbConn, chatId: string) =
let query = fmt"""
UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND seen != 1"""
db.exec(query, chatId)
let chatQuery = fmt"""
UPDATE chats SET unviewed_message_count = 0 WHERE id = ?"""
db.exec(chatQuery, chatId)
proc markMessagesSeen*(db: DbConn, chatId: string, messageIds: seq[string]) =
let quotedIds = sequtils.map(messageIds, proc(s:string):string = "'" & s & "'")
let inVector = strutils.join(quotedIds, ",")
let query = fmt"UPDATE user_messages SET seen = 1 WHERE id IN (" & inVector & ")"
db.exec(query)
let chatQuery = fmt"""
UPDATE chats SET unviewed_message_count =
(SELECT COUNT(1)
FROM user_messages
WHERE local_chat_id = ? AND seen = 0)
WHERE id = ?"""
db.exec(chatQuery, chatId, chatId)

56
test/nim/chats.nim Normal file
View File

@ -0,0 +1,56 @@
import # nim libs
os, json, options
import # vendor libs
sqlcipher, json_serialization, web3/conversions as web3_conversions
import # nim-status libs
../../nim_status/lib/[chats, database, conversions]
let passwd = "qwerty"
let path = currentSourcePath.parentDir() & "/build/myDatabase"
let db = initializeDB(path, passwd)
var chat = Chat(
id: "local-chat-id",
name: "chat-name",
color: "blue",
chatType: 1,
active: true,
timestamp: 25,
deletedAtClockValue: 15,
publicKey: cast[seq[byte]]("public-key"),
unviewedMessageCount: 3,
lastClockValue: 18,
lastMessage: cast[seq[byte]]("lastMessage"),
members: cast[seq[byte]]("members"),
membershipUpdates: cast[seq[byte]]("membershipUpdates"),
profile: "profile",
invitationAdmin: "invitationAdmin",
muted: false,
)
# saveChat
db.saveChat(chat)
# getChats
chat.id = "local-chat-id-1"
db.saveChat(chat)
var dbChats = db.getChats()
assert len(dbChats) == 2
# getChatById
var dbChat = db.getChatById("local-chat-id").get()
assert dbChat.active == true and
dbChat.publicKey == cast[seq[byte]]("public-key") and
dbChat.unviewedMessageCount == 3
# deleteChat
db.deleteChat(chat)
dbChats = db.getChats()
assert len(dbChats) == 1
db.close()
removeFile(path)

110
test/nim/messages.nim Normal file
View File

@ -0,0 +1,110 @@
import # nim libs
os, json, options
import # vendor libs
sqlcipher, json_serialization, web3/conversions as web3_conversions
import # nim-status libs
../../nim_status/lib/[messages, database, conversions, chats]
let passwd = "qwerty"
let path = currentSourcePath.parentDir() & "/build/myDatabase"
let db = initializeDB(path, passwd)
var msg = Message(
id: "msg1",
whisperTimestamp: 0,
source: "default_source",
destination: cast[seq[byte]]("default_destination"),
text: "text",
contentType: 0,
username: "user1",
timestamp: 25,
chatId: "chat-id",
localChatId: "local-chat-id",
hide: false,
responseTo: "user1",
messageType: 0,
clockValue: 0,
seen: false,
outgoingStatus: "Delivered",
parsedText: cast[seq[byte]]("parsed"),
rawPayload: cast[seq[byte]]("raw"),
stickerPack: 0,
stickerHash: "hash",
commandId: "command1",
commandValue: "commandValue",
commandAddress: "commandAddress",
commandFrom: "commandFrom",
commandContract: "commandContract",
commandTransactionHash: "commandTransactionHash",
commandSignature: cast[seq[byte]]("commandSignature"),
commandState: 3,
audioPayload: cast[seq[byte]]("audioPayload"),
audioType: 0,
audioDurationMs: 10,
audioBase64: "sdf",
replaceMessage: "message_replacement",
rtl: false,
lineCount: 5,
links: "links",
mentions: "mentions",
imagePayload: cast[seq[byte]]("blob"),
imageType: "type",
imageBase64: "sdfsdfsdf"
)
# saveMessage
db.saveMessage(msg)
# getMessageById
var dbMsg = db.getMessageById("msg1").get()
assert dbMsg.links == "links" and
dbMsg.timestamp == 25 and
dbMsg.username == "user1"
# markAllRead
db.markAllRead("local-chat-id")
dbMsg = db.getMessageById("msg1").get()
assert dbMsg.seen == true
# markMessagesSeen
msg.id = "msg2"
db.saveMessage(msg)
var chat = Chat(
id: "local-chat-id",
name: "chat-name",
color: "blue",
chatType: 1,
active: true,
timestamp: 25,
deletedAtClockValue: 15,
publicKey: cast[seq[byte]]("public-key"),
unviewedMessageCount: 3,
lastClockValue: 18,
lastMessage: cast[seq[byte]]("lastMessage"),
members: cast[seq[byte]]("members"),
membershipUpdates: cast[seq[byte]]("membershipUpdates"),
profile: "profile",
invitationAdmin: "invitationAdmin",
muted: false,
)
db.saveChat(chat)
db.markMessagesSeen("local-chat-id", @["msg1", "msg2"])
dbMsg = db.getMessageById("msg2").get()
var dbChat = db.getChatById("local-chat-id").get()
assert dbMsg.seen == true and dbChat.unviewedMessageCount == 0
# deleteMessage
db.deleteMessage(msg)
var found: bool
try:
discard db.getMessageById("msg2").get()
found = true
except:
found = false
assert found == false
db.close()
removeFile(path)