Move contacts to status-protocol-go & add delete chat endpoint

Adds to api calls, Contacts() not paginated and SaveContact()

DeviceInfo and SystemTags have been stored as a blob to simplify
querying, although at some point we might want to query system tags (for
example to fetch all the blocked/added by us/added by them contacts),
but not a use case for now.
This commit is contained in:
Andrea Maria Piana 2019-08-02 09:51:08 +02:00
parent b742356b68
commit baa579640f
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
7 changed files with 330 additions and 8 deletions

33
contact.go Normal file
View File

@ -0,0 +1,33 @@
package statusproto
// ContactDeviceInfo is a struct containing information about a particular device owned by a contact
type ContactDeviceInfo struct {
// The installation id of the device
InstallationID string `json:"id"`
// Timestamp represents the last time we received this info
Timestamp int64 `json:"timestamp"`
// FCMToken is to be used for push notifications
FCMToken string `json:"fcmToken"`
}
// Contact has information about a "Contact". A contact is not necessarily one
// that we added or added us, that's based on SystemTags.
type Contact struct {
// ID of the contact
ID string `json:"id"`
// Ethereum address of the contact
Address string `json:"address"`
// Name of contact
Name string `json:"name"`
// Photo is the base64 encoded photo
Photo string `json:"photoPath"`
// LastUpdated is the last time we received an update from the contact
// updates should be discarded if last updated is less than the one stored
LastUpdated int64 `json:"lastUpdated"`
// SystemTags contains information about whether we blocked/added/have been
// added.
SystemTags []string `json:"systemTags"`
DeviceInfo []ContactDeviceInfo `json:"deviceInfo"`
TributeToTalk string `json:"tributeToTalk"`
}

View File

@ -391,6 +391,18 @@ func (m *Messenger) Chats(from, to int) ([]*Chat, error) {
return m.persistence.Chats(from, to)
}
func (m *Messenger) DeleteChat(chatID string, chatType ChatType) error {
return m.persistence.DeleteChat(chatID, chatType)
}
func (m *Messenger) SaveContact(contact Contact) error {
return m.persistence.SaveContact(contact)
}
func (m *Messenger) Contacts() ([]*Contact, error) {
return m.persistence.Contacts()
}
func (m *Messenger) Send(ctx context.Context, chat Chat, data []byte) ([]byte, error) {
chatID := chat.ID
if chatID == "" {

View File

@ -156,6 +156,33 @@ func (s *MessengerSuite) TestChatPersistencePublic() {
s.Require().Equal(actualChat, expectedChat)
}
func (s *MessengerSuite) TestDeleteChat() {
chatID := "chatid"
chat := Chat{
ID: chatID,
Name: "chat-name",
Color: "#fffff",
Active: true,
ChatType: ChatTypePublic,
Timestamp: 10,
LastClockValue: 20,
DeletedAtClockValue: 30,
UnviewedMessagesCount: 40,
LastMessageContentType: "something",
LastMessageContent: "something-else",
}
s.Require().NoError(s.m.SaveChat(chat))
savedChats, err := s.m.Chats(0, 10)
s.Require().NoError(err)
s.Require().Equal(1, len(savedChats))
s.Require().NoError(s.m.DeleteChat(chatID, ChatTypePublic))
savedChats, err = s.m.Chats(0, 10)
s.Require().NoError(err)
s.Require().Equal(0, len(savedChats))
}
func (s *MessengerSuite) TestChatPersistenceUpdate() {
chat := Chat{
ID: "chat-name",
@ -321,6 +348,85 @@ func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() {
s.Require().Equal(expectedChat, actualChat)
}
func (s *MessengerSuite) TestContactPersistence() {
contact := Contact{
ID: "contact-id",
Address: "contact-address",
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{"1", "2"},
DeviceInfo: []ContactDeviceInfo{
ContactDeviceInfo{
InstallationID: "1",
Timestamp: 2,
FCMToken: "token",
},
ContactDeviceInfo{
InstallationID: "2",
Timestamp: 3,
FCMToken: "token-2",
},
},
TributeToTalk: "talk",
}
s.Require().NoError(s.m.SaveContact(contact))
savedContacts, err := s.m.Contacts()
s.Require().NoError(err)
s.Require().Equal(1, len(savedContacts))
actualContact := savedContacts[0]
expectedContact := &contact
s.Require().Equal(expectedContact, actualContact)
}
func (s *MessengerSuite) TestContactPersistenceUpdate() {
contact := Contact{
ID: "contact-id",
Address: "contact-address",
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{"1", "2"},
DeviceInfo: []ContactDeviceInfo{
ContactDeviceInfo{
InstallationID: "1",
Timestamp: 2,
FCMToken: "token",
},
ContactDeviceInfo{
InstallationID: "2",
Timestamp: 3,
FCMToken: "token-2",
},
},
TributeToTalk: "talk",
}
s.Require().NoError(s.m.SaveContact(contact))
savedContacts, err := s.m.Contacts()
s.Require().NoError(err)
s.Require().Equal(1, len(savedContacts))
actualContact := savedContacts[0]
expectedContact := &contact
s.Require().Equal(expectedContact, actualContact)
contact.Name = "updated-name"
s.Require().NoError(s.m.SaveContact(contact))
updatedContact, err := s.m.Contacts()
s.Require().NoError(err)
s.Require().Equal(1, len(updatedContact))
actualUpdatedContact := updatedContact[0]
expectedUpdatedContact := &contact
s.Require().Equal(expectedUpdatedContact, actualUpdatedContact)
}
func (s *MessengerSuite) TestSharedSecretHandler() {
err := s.m.handleSharedSecrets(nil)
s.NoError(err)

View File

@ -4,6 +4,8 @@
// 000001_init.up.db.sql (840B)
// 000002_add_chats.down.db.sql (74B)
// 000002_add_chats.up.db.sql (541B)
// 000003_add_contacts.down.db.sql (21B)
// 000003_add_contacts.up.db.sql (251B)
// doc.go (377B)
package migrations
@ -128,7 +130,7 @@ func _000002_add_chatsDownDbSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_add_chats.down.db.sql", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1564502835, 0)}
info := bindataFileInfo{name: "000002_add_chats.down.db.sql", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1564587343, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd3, 0xa7, 0xf0, 0x94, 0x7a, 0x9, 0xdc, 0x6c, 0x7b, 0xdc, 0x12, 0x30, 0x55, 0x31, 0x17, 0xf2, 0xcc, 0x6e, 0xfd, 0xbb, 0x70, 0xb9, 0xd8, 0x9f, 0x81, 0x83, 0xdc, 0x1d, 0x1c, 0x3a, 0x8d, 0xce}}
return a, nil
}
@ -148,11 +150,51 @@ func _000002_add_chatsUpDbSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "000002_add_chats.up.db.sql", size: 541, mode: os.FileMode(0644), modTime: time.Unix(1564587096, 0)}
info := bindataFileInfo{name: "000002_add_chats.up.db.sql", size: 541, mode: os.FileMode(0644), modTime: time.Unix(1564587387, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd, 0x7f, 0x3a, 0xd7, 0xf6, 0x8b, 0x6e, 0x4d, 0xce, 0x7d, 0x63, 0x1d, 0x30, 0xa2, 0xc1, 0xb, 0xa0, 0x35, 0x2e, 0xfa, 0xef, 0xf0, 0x39, 0xf7, 0x22, 0xdd, 0x31, 0x11, 0xb1, 0xff, 0xbf, 0xb3}}
return a, nil
}
var __000003_add_contactsDownDbSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\x2b\x49\x4c\x2e\x29\xb6\xe6\x02\x04\x00\x00\xff\xff\x66\x64\xd9\xdd\x15\x00\x00\x00")
func _000003_add_contactsDownDbSqlBytes() ([]byte, error) {
return bindataRead(
__000003_add_contactsDownDbSql,
"000003_add_contacts.down.db.sql",
)
}
func _000003_add_contactsDownDbSql() (*asset, error) {
bytes, err := _000003_add_contactsDownDbSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_add_contacts.down.db.sql", size: 21, mode: os.FileMode(0644), modTime: time.Unix(1564721146, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xfc, 0x7e, 0xb, 0xec, 0x72, 0xcd, 0x21, 0x3e, 0xa2, 0x38, 0xe0, 0x95, 0x7e, 0xce, 0x4a, 0x17, 0xc8, 0xd0, 0x1c, 0xfa, 0xa3, 0x23, 0x5, 0xab, 0x89, 0xf9, 0xfc, 0x63, 0x7, 0x28, 0xe9, 0x93}}
return a, nil
}
var __000003_add_contactsUpDbSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\xcd\xc1\x4a\xc4\x30\x14\x85\xe1\x7d\x9e\xe2\x2c\x15\x5c\xb8\x77\x95\xc6\x3b\x50\x8c\xc9\x10\x32\xe0\xac\x42\x6c\xa2\x06\xa7\x4d\x69\x6e\x05\xdf\x5e\x8a\xa0\x0c\xdd\x7e\xe7\x87\xa3\x1c\x49\x4f\xf0\xb2\xd3\x84\xa1\x4e\x1c\x07\x6e\xb8\x11\x40\x49\xf0\xf4\xe2\x71\x74\xfd\xb3\x74\x67\x3c\xd1\x19\xd6\x40\x59\x73\xd0\xbd\xf2\x70\x74\xd4\x52\xd1\x9d\x00\x62\x4a\x4b\x6e\xed\xb7\x37\xd6\xc3\x9c\xb4\xde\x86\x29\x8e\x79\xaf\xf3\x47\xe5\xba\xe7\x4b\x6c\x1c\xd6\x39\x45\xce\x09\xbd\xf9\x1f\xf1\x48\x07\x79\xd2\x1e\xf7\x5b\xd6\xbe\x1b\xe7\x31\x70\x7c\x6f\xe8\xb4\xed\x36\x4b\xf9\xab\x0c\x39\x94\xe9\xad\xfe\x19\x2f\xe5\x75\xe5\x1c\xb8\x06\x8e\x97\xcf\xeb\x3f\x71\xfb\x20\x7e\x02\x00\x00\xff\xff\xc3\x2e\x5b\xed\xfb\x00\x00\x00")
func _000003_add_contactsUpDbSqlBytes() ([]byte, error) {
return bindataRead(
__000003_add_contactsUpDbSql,
"000003_add_contacts.up.db.sql",
)
}
func _000003_add_contactsUpDbSql() (*asset, error) {
bytes, err := _000003_add_contactsUpDbSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_add_contacts.up.db.sql", size: 251, mode: os.FileMode(0644), modTime: time.Unix(1564721109, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0x19, 0x9f, 0x5c, 0x9d, 0xa1, 0xe5, 0x99, 0xbe, 0x47, 0xce, 0xa5, 0xd3, 0x51, 0x2f, 0x9b, 0x1d, 0xd9, 0x3f, 0x7a, 0xbf, 0xf, 0x76, 0x6b, 0x4f, 0x82, 0xbd, 0x13, 0x9d, 0x25, 0xdd, 0x60}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8f\xbb\x6e\xc3\x30\x0c\x45\x77\x7f\xc5\x45\x96\x2c\xb5\xb4\x74\xea\xd6\xb1\x7b\x7f\x80\x91\x68\x89\x88\x1e\xae\x48\xe7\xf1\xf7\x85\xd3\x02\xcd\xd6\xf5\x00\xe7\xf0\xd2\x7b\x7c\x66\x51\x2c\x52\x18\xa2\x68\x1c\x58\x95\xc6\x1d\x27\x0e\xb4\x29\xe3\x90\xc4\xf2\x76\x72\xa1\x57\xaf\x46\xb6\xe9\x2c\xd5\x57\x49\x83\x8c\xfd\xe5\xf5\x30\x79\x8f\x40\xed\x68\xc8\xd4\x62\xe1\x47\x4b\xa1\x46\xc3\xa4\x25\x5c\xc5\x32\x08\xeb\xe0\x45\x6e\x0e\xef\x86\xc2\xa4\x06\xcb\x64\x47\x85\x65\x46\x20\xe5\x3d\xb3\xf4\x81\xd4\xe7\x93\xb4\x48\x46\x6e\x47\x1f\xcb\x13\xd9\x17\x06\x2a\x85\x23\x96\xd1\xeb\xc3\x55\xaa\x8c\x28\x83\x83\xf5\x71\x7f\x01\xa9\xb2\xa1\x51\x65\xdd\xfd\x4c\x17\x46\xeb\xbf\xe7\x41\x2d\xfe\xff\x11\xae\x7d\x9c\x15\xa4\xe0\xdb\xca\xc1\x38\xba\x69\x5a\x29\x9c\x29\x31\xf4\xab\x88\xf1\x34\x79\x9f\xfa\x5b\xe2\xc6\xbb\xf5\xbc\x71\x5e\xcf\x09\x3f\x35\xe9\x4d\x31\x77\x38\xe7\xff\x80\x4b\x1d\x6e\xfa\x0e\x00\x00\xff\xff\x9d\x60\x3d\x88\x79\x01\x00\x00")
func docGoBytes() ([]byte, error) {
@ -272,6 +314,10 @@ var _bindata = map[string]func() (*asset, error){
"000002_add_chats.up.db.sql": _000002_add_chatsUpDbSql,
"000003_add_contacts.down.db.sql": _000003_add_contactsDownDbSql,
"000003_add_contacts.up.db.sql": _000003_add_contactsUpDbSql,
"doc.go": docGo,
}
@ -316,11 +362,13 @@ type bintree struct {
}
var _bintree = &bintree{nil, map[string]*bintree{
"000001_init.down.db.sql": &bintree{_000001_initDownDbSql, map[string]*bintree{}},
"000001_init.up.db.sql": &bintree{_000001_initUpDbSql, map[string]*bintree{}},
"000002_add_chats.down.db.sql": &bintree{_000002_add_chatsDownDbSql, map[string]*bintree{}},
"000002_add_chats.up.db.sql": &bintree{_000002_add_chatsUpDbSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
"000001_init.down.db.sql": &bintree{_000001_initDownDbSql, map[string]*bintree{}},
"000001_init.up.db.sql": &bintree{_000001_initUpDbSql, map[string]*bintree{}},
"000002_add_chats.down.db.sql": &bintree{_000002_add_chatsDownDbSql, map[string]*bintree{}},
"000002_add_chats.up.db.sql": &bintree{_000002_add_chatsUpDbSql, map[string]*bintree{}},
"000003_add_contacts.down.db.sql": &bintree{_000003_add_contactsDownDbSql, map[string]*bintree{}},
"000003_add_contacts.up.db.sql": &bintree{_000003_add_contactsUpDbSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.

View File

@ -0,0 +1 @@
DROP TABLE contacts;

View File

@ -0,0 +1,10 @@
CREATE TABLE contacts (
id TEXT PRIMARY KEY ON CONFLICT REPLACE,
address TEXT NOT NULL,
name TEXT NOT NULL,
photo TEXT NOT NULL,
last_updated INT NOT NULL DEFAULT 0,
system_tags BLOB,
device_info BLOB,
tribute_to_talk TEXT NOT NULL
);

View File

@ -97,10 +97,14 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
return
}
func formatChatID(chatID string, chatType ChatType) string {
return fmt.Sprintf("%s-%d", chatID, chatType)
}
func (db sqlitePersistence) SaveChat(chat Chat) error {
var err error
// We build the db chatID using the type, so that we have no clashes
chatID := fmt.Sprintf("%s-%d", chat.ID, chat.ChatType)
chatID := formatChatID(chat.ID, chat.ChatType)
pkey := []byte{}
// For one to one chatID is an encoded public key
@ -164,6 +168,12 @@ func (db sqlitePersistence) SaveChat(chat Chat) error {
return err
}
func (db sqlitePersistence) DeleteChat(chatID string, chatType ChatType) error {
dbChatID := formatChatID(chatID, chatType)
_, err := db.db.Exec("DELETE FROM chats WHERE id = ?", dbChatID)
return err
}
func (db sqlitePersistence) Chats(from, to int) ([]*Chat, error) {
rows, err := db.db.Query(`SELECT
@ -242,6 +252,108 @@ func (db sqlitePersistence) Chats(from, to int) ([]*Chat, error) {
return response, nil
}
func (db sqlitePersistence) Contacts() ([]*Contact, error) {
rows, err := db.db.Query(`SELECT
id,
address,
name,
photo,
last_updated,
system_tags,
device_info,
tribute_to_talk
FROM contacts`)
if err != nil {
return nil, err
}
defer rows.Close()
var response []*Contact
for rows.Next() {
contact := &Contact{}
encodedDeviceInfo := []byte{}
encodedSystemTags := []byte{}
err := rows.Scan(
&contact.ID,
&contact.Address,
&contact.Name,
&contact.Photo,
&contact.LastUpdated,
&encodedSystemTags,
&encodedDeviceInfo,
&contact.TributeToTalk,
)
if err != nil {
return nil, err
}
// Restore device info
deviceInfoDecoder := gob.NewDecoder(bytes.NewBuffer(encodedDeviceInfo))
if err := deviceInfoDecoder.Decode(&contact.DeviceInfo); err != nil {
return nil, err
}
// Restore system tags
systemTagsDecoder := gob.NewDecoder(bytes.NewBuffer(encodedSystemTags))
if err := systemTagsDecoder.Decode(&contact.SystemTags); err != nil {
return nil, err
}
response = append(response, contact)
}
return response, nil
}
func (db sqlitePersistence) SaveContact(contact Contact) error {
// Encode device info
var encodedDeviceInfo bytes.Buffer
deviceInfoEncoder := gob.NewEncoder(&encodedDeviceInfo)
if err := deviceInfoEncoder.Encode(contact.DeviceInfo); err != nil {
return err
}
// Encoded system tags
var encodedSystemTags bytes.Buffer
systemTagsEncoder := gob.NewEncoder(&encodedSystemTags)
if err := systemTagsEncoder.Encode(contact.SystemTags); err != nil {
return err
}
// Insert record
stmt, err := db.db.Prepare(`INSERT INTO contacts(
id,
address,
name,
photo,
last_updated,
system_tags,
device_info,
tribute_to_talk
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(
contact.ID,
contact.Address,
contact.Name,
contact.Photo,
contact.LastUpdated,
encodedSystemTags.Bytes(),
encodedDeviceInfo.Bytes(),
contact.TributeToTalk,
)
return err
}
// Messages returns messages for a given contact, in a given period. Ordered by a timestamp.
func (db sqlitePersistence) Messages(chatID string, from, to time.Time) (result []*protocol.Message, err error) {
rows, err := db.db.Query(`SELECT