group chat invitation

This commit is contained in:
andrey 2020-08-07 15:49:37 +02:00 committed by flexsurfer
parent 2eaa1fcad7
commit 3b748a2e46
19 changed files with 774 additions and 46 deletions

View File

@ -1 +1 @@
0.59.0 0.60.0

View File

@ -67,6 +67,9 @@ type Chat struct {
// Muted is used to check whether we want to receive // Muted is used to check whether we want to receive
// push notifications for this chat // push notifications for this chat
Muted bool `json:"muted,omitempty"` Muted bool `json:"muted,omitempty"`
// Public key of administrator who created invitation link
InvitationAdmin string `json:"invitationAdmin,omitempty"`
} }
func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) { func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) {

View File

@ -0,0 +1,60 @@
package protocol
import (
"crypto/ecdsa"
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/protobuf"
)
// Invitation represents a group chat invitation request from a user in the application layer, used for persistence, querying and
// signaling
type GroupChatInvitation struct {
protobuf.GroupChatInvitation
// From is a public key of the author of the invitation request.
From string `json:"from,omitempty"`
// SigPubKey is the ecdsa encoded public key of the invitation author
SigPubKey *ecdsa.PublicKey `json:"-"`
}
// ID is the Keccak256() contatenation of From-ChatId
func (g GroupChatInvitation) ID() string {
return types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s", g.From, g.ChatId))))
}
// GetSigPubKey returns an ecdsa encoded public key
// this function is required to implement the ChatEntity interface
func (g GroupChatInvitation) GetSigPubKey() *ecdsa.PublicKey {
return g.SigPubKey
}
// GetProtoBuf returns the struct's embedded protobuf struct
// this function is required to implement the ChatEntity interface
func (g GroupChatInvitation) GetProtobuf() proto.Message {
return &g.GroupChatInvitation
}
func (g GroupChatInvitation) MarshalJSON() ([]byte, error) {
item := struct {
ID string `json:"id"`
ChatID string `json:"chatId,omitempty"`
From string `json:"from"`
IntroductionMessage string `json:"introductionMessage,omitempty"`
State protobuf.GroupChatInvitation_State `json:"state,omitempty"`
}{
ID: g.ID(),
ChatID: g.ChatId,
From: g.From,
IntroductionMessage: g.IntroductionMessage,
State: g.State,
}
return json.Marshal(item)
}

View File

@ -9,6 +9,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
@ -54,10 +55,38 @@ func (m *MessageHandler) HandleMembershipUpdate(messageState *ReceivedMessageSta
return err return err
} }
if chat == nil { //if chat.InvitationAdmin exists means we are waiting for invitation request approvement, and in that case
//we need to create a new chat instance like we don't have a chat and just use a regular invitation flow
if chat == nil || len(chat.InvitationAdmin) > 0 {
if len(message.Events) == 0 { if len(message.Events) == 0 {
return errors.New("can't create new group chat without events") return errors.New("can't create new group chat without events")
} }
//approve invitations
if chat != nil && len(chat.InvitationAdmin) > 0 {
groupChatInvitation := &GroupChatInvitation{
GroupChatInvitation: protobuf.GroupChatInvitation{
ChatId: message.ChatID,
},
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
if err != nil && err != errRecordNotFound {
return err
}
if groupChatInvitation != nil {
groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED
err := m.persistence.SaveInvitation(groupChatInvitation)
if err != nil {
return err
}
messageState.GroupChatInvitations[groupChatInvitation.ID()] = groupChatInvitation
}
}
group, err = v1protocol.NewGroupWithEvents(message.ChatID, message.Events) group, err = v1protocol.NewGroupWithEvents(message.ChatID, message.Events)
if err != nil { if err != nil {
return err return err
@ -69,7 +98,6 @@ func (m *MessageHandler) HandleMembershipUpdate(messageState *ReceivedMessageSta
} }
newChat := CreateGroupChat(messageState.Timesource) newChat := CreateGroupChat(messageState.Timesource)
chat = &newChat chat = &newChat
} else { } else {
existingGroup, err := newProtocolGroupFromChat(chat) existingGroup, err := newProtocolGroupFromChat(chat)
if err != nil { if err != nil {
@ -724,3 +752,45 @@ func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, pbEmoj
return nil return nil
} }
func (m *MessageHandler) HandleGroupChatInvitation(state *ReceivedMessageState, pbGHInvitations protobuf.GroupChatInvitation) error {
logger := m.logger.With(zap.String("site", "HandleGroupChatInvitation"))
if err := ValidateReceivedGroupChatInvitation(&pbGHInvitations); err != nil {
logger.Error("invalid group chat invitation", zap.Error(err))
return err
}
groupChatInvitation := &GroupChatInvitation{
GroupChatInvitation: pbGHInvitations,
SigPubKey: state.CurrentMessageState.PublicKey,
}
//From is the PK of author of invitation request
if groupChatInvitation.State == protobuf.GroupChatInvitation_REJECTED {
//rejected so From is the current user who received this rejection
groupChatInvitation.From = types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
} else {
//invitation request, so From is the author of message
groupChatInvitation.From = state.CurrentMessageState.Contact.ID
}
existingInvitation, err := m.persistence.InvitationByID(groupChatInvitation.ID())
if err != errRecordNotFound && err != nil {
return err
}
if existingInvitation != nil && existingInvitation.Clock >= pbGHInvitations.Clock {
// this is not a valid invitation, ignoring
return nil
}
// save invitation
err = m.persistence.SaveInvitation(groupChatInvitation)
if err != nil {
return err
}
state.GroupChatInvitations[groupChatInvitation.ID()] = groupChatInvitation
return nil
}

View File

@ -835,3 +835,96 @@ func (db sqlitePersistence) EmojiReactionByID(id string) (*EmojiReaction, error)
return nil, err return nil, err
} }
} }
func (db sqlitePersistence) SaveInvitation(invitation *GroupChatInvitation) (err error) {
query := "INSERT INTO group_chat_invitations(id,source,chat_id,message,state,clock) VALUES (?,?,?,?,?,?)"
stmt, err := db.db.Prepare(query)
if err != nil {
return
}
_, err = stmt.Exec(
invitation.ID(),
invitation.From,
invitation.ChatId,
invitation.IntroductionMessage,
invitation.State,
invitation.Clock,
)
return
}
func (db sqlitePersistence) GetGroupChatInvitations() (rst []*GroupChatInvitation, err error) {
tx, err := db.db.Begin()
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
bRows, err := tx.Query(`SELECT
source,
chat_id,
message,
state,
clock
FROM
group_chat_invitations`)
if err != nil {
return
}
defer bRows.Close()
for bRows.Next() {
invitation := GroupChatInvitation{}
err = bRows.Scan(
&invitation.From,
&invitation.ChatId,
&invitation.IntroductionMessage,
&invitation.State,
&invitation.Clock)
if err != nil {
return nil, err
}
rst = append(rst, &invitation)
}
return rst, nil
}
func (db sqlitePersistence) InvitationByID(id string) (*GroupChatInvitation, error) {
row := db.db.QueryRow(
`SELECT
source,
chat_id,
message,
state,
clock
FROM
group_chat_invitations
WHERE
group_chat_invitations.id = ?
`, id)
chatInvitations := new(GroupChatInvitation)
err := row.Scan(&chatInvitations.From,
&chatInvitations.ChatId,
&chatInvitations.IntroductionMessage,
&chatInvitations.State,
&chatInvitations.Clock,
)
switch err {
case sql.ErrNoRows:
return nil, errRecordNotFound
case nil:
return chatInvitations, nil
default:
return nil, err
}
}

View File

@ -256,3 +256,12 @@ func ValidateReceivedEmojiReaction(emoji *protobuf.EmojiReaction, whisperTimesta
return nil return nil
} }
func ValidateReceivedGroupChatInvitation(invitation *protobuf.GroupChatInvitation) error {
if len(invitation.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
return nil
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"database/sql" "database/sql"
"encoding/hex"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"os" "os"
@ -94,10 +95,11 @@ type MessengerResponse struct {
Contacts []*Contact `json:"contacts,omitempty"` Contacts []*Contact `json:"contacts,omitempty"`
Installations []*multidevice.Installation `json:"installations,omitempty"` Installations []*multidevice.Installation `json:"installations,omitempty"`
EmojiReactions []*EmojiReaction `json:"emojiReactions,omitempty"` EmojiReactions []*EmojiReaction `json:"emojiReactions,omitempty"`
Invitations []*GroupChatInvitation `json:"invitations,omitempty"`
} }
func (m *MessengerResponse) IsEmpty() bool { func (m *MessengerResponse) IsEmpty() bool {
return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.Installations) == 0 return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.Installations) == 0 && len(m.Invitations) == 0
} }
type dbConfig struct { type dbConfig struct {
@ -768,6 +770,23 @@ func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string,
return &response, m.saveChat(&chat) return &response, m.saveChat(&chat)
} }
func (m *Messenger) CreateGroupChatFromInvitation(name string, chatID string, adminPK string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
var response MessengerResponse
logger := m.logger.With(zap.String("site", "CreateGroupChatFromInvitation"))
logger.Info("Creating group chat from invitation", zap.String("name", name))
chat := CreateGroupChat(m.getTimesource())
chat.ID = chatID
chat.Name = name
chat.InvitationAdmin = adminPK
response.Chats = []*Chat{&chat}
return &response, m.saveChat(&chat)
}
func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string, member string) (*MessengerResponse, error) { func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string, member string) (*MessengerResponse, error) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
@ -859,6 +878,32 @@ func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, me
return nil, err return nil, err
} }
//approve invitations
for _, member := range members {
logger.Info("ApproveInvitationByChatIdAndFrom", zap.String("chatID", chatID), zap.Any("member", member))
groupChatInvitation := &GroupChatInvitation{
GroupChatInvitation: protobuf.GroupChatInvitation{
ChatId: chat.ID,
},
From: member,
}
groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
if err != nil && err != errRecordNotFound {
return nil, err
}
if groupChatInvitation != nil {
groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED
err := m.persistence.SaveInvitation(groupChatInvitation)
if err != nil {
return nil, err
}
response.Invitations = append(response.Invitations, groupChatInvitation)
}
}
err = group.ProcessEvent(event) err = group.ProcessEvent(event)
if err != nil { if err != nil {
return nil, err return nil, err
@ -963,6 +1008,151 @@ func (m *Messenger) ChangeGroupChatName(ctx context.Context, chatID string, name
return &response, m.saveChat(chat) return &response, m.saveChat(chat)
} }
func (m *Messenger) SendGroupChatInvitationRequest(ctx context.Context, chatID string, adminPK string,
message string) (*MessengerResponse, error) {
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRequest"))
logger.Info("Sending group chat invitation request", zap.String("chatID", chatID),
zap.String("adminPK", adminPK), zap.String("message", message))
m.mutex.Lock()
defer m.mutex.Unlock()
var response MessengerResponse
// Get chat and clock
chat, ok := m.allChats[chatID]
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
invitationR := &GroupChatInvitation{
GroupChatInvitation: protobuf.GroupChatInvitation{
Clock: clock,
ChatId: chatID,
IntroductionMessage: message,
State: protobuf.GroupChatInvitation_REQUEST,
},
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
if err != nil {
return nil, err
}
spec := common.RawMessage{
LocalChatID: adminPK,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
ResendAutomatically: true,
}
pkey, err := hex.DecodeString(adminPK[2:])
if err != nil {
return nil, err
}
// Safety check, make sure is well formed
adminpk, err := crypto.UnmarshalPubkey(pkey)
if err != nil {
return nil, err
}
id, err := m.processor.SendPrivate(ctx, adminpk, spec)
if err != nil {
return nil, err
}
spec.ID = types.EncodeHex(id)
spec.SendCount++
err = m.persistence.SaveRawMessage(&spec)
if err != nil {
return nil, err
}
response.Invitations = []*GroupChatInvitation{invitationR}
err = m.persistence.SaveInvitation(invitationR)
if err != nil {
return nil, err
}
return &response, nil
}
func (m *Messenger) GetGroupChatInvitations() ([]*GroupChatInvitation, error) {
return m.persistence.GetGroupChatInvitations()
}
func (m *Messenger) SendGroupChatInvitationRejection(ctx context.Context, invitationRequestID string) (*MessengerResponse, error) {
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRejection"))
logger.Info("Sending group chat invitation reject", zap.String("invitationRequestID", invitationRequestID))
m.mutex.Lock()
defer m.mutex.Unlock()
invitationR, err := m.persistence.InvitationByID(invitationRequestID)
if err != nil {
return nil, err
}
invitationR.State = protobuf.GroupChatInvitation_REJECTED
// Get chat and clock
chat, ok := m.allChats[invitationR.ChatId]
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
invitationR.Clock = clock
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
if err != nil {
return nil, err
}
spec := common.RawMessage{
LocalChatID: invitationR.From,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
ResendAutomatically: true,
}
pkey, err := hex.DecodeString(invitationR.From[2:])
if err != nil {
return nil, err
}
// Safety check, make sure is well formed
userpk, err := crypto.UnmarshalPubkey(pkey)
if err != nil {
return nil, err
}
id, err := m.processor.SendPrivate(ctx, userpk, spec)
if err != nil {
return nil, err
}
spec.ID = types.EncodeHex(id)
spec.SendCount++
err = m.persistence.SaveRawMessage(&spec)
if err != nil {
return nil, err
}
var response MessengerResponse
response.Invitations = []*GroupChatInvitation{invitationR}
err = m.persistence.SaveInvitation(invitationR)
if err != nil {
return nil, err
}
return &response, nil
}
func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) { func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
@ -1920,6 +2110,8 @@ type ReceivedMessageState struct {
// EmojiReactions is a list of emoji reactions for the current batch // EmojiReactions is a list of emoji reactions for the current batch
// indexed by from-message-id-emoji-type // indexed by from-message-id-emoji-type
EmojiReactions map[string]*EmojiReaction EmojiReactions map[string]*EmojiReaction
// GroupChatInvitations is a list of invitation requests or rejections
GroupChatInvitations map[string]*GroupChatInvitation
// Response to the client // Response to the client
Response *MessengerResponse Response *MessengerResponse
// Timesource is a time source for clock values/timestamps. // Timesource is a time source for clock values/timestamps.
@ -1938,6 +2130,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
ModifiedInstallations: m.modifiedInstallations, ModifiedInstallations: m.modifiedInstallations,
ExistingMessagesMap: make(map[string]bool), ExistingMessagesMap: make(map[string]bool),
EmojiReactions: make(map[string]*EmojiReaction), EmojiReactions: make(map[string]*EmojiReaction),
GroupChatInvitations: make(map[string]*GroupChatInvitation),
Response: &MessengerResponse{}, Response: &MessengerResponse{},
Timesource: m.getTimesource(), Timesource: m.getTimesource(),
} }
@ -2202,6 +2395,13 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Warn("failed to handle EmojiReaction", zap.Error(err)) logger.Warn("failed to handle EmojiReaction", zap.Error(err))
continue continue
} }
case protobuf.GroupChatInvitation:
logger.Debug("Handling GroupChatInvitation")
err = m.handler.HandleGroupChatInvitation(messageState, msg.ParsedMessage.Interface().(protobuf.GroupChatInvitation))
if err != nil {
logger.Warn("failed to handle GroupChatInvitation", zap.Error(err))
continue
}
default: default:
// Check if is an encrypted PushNotificationRegistration // Check if is an encrypted PushNotificationRegistration
@ -2284,6 +2484,10 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
messageState.Response.EmojiReactions = append(messageState.Response.EmojiReactions, emojiReaction) messageState.Response.EmojiReactions = append(messageState.Response.EmojiReactions, emojiReaction)
} }
for _, groupChatInvitation := range messageState.GroupChatInvitations {
messageState.Response.Invitations = append(messageState.Response.Invitations, groupChatInvitation)
}
if len(contactsToSave) > 0 { if len(contactsToSave) > 0 {
err = m.persistence.SaveContacts(contactsToSave) err = m.persistence.SaveContacts(contactsToSave)
if err != nil { if err != nil {

View File

@ -18,6 +18,9 @@
// 1595862781_add_audio_data.up.sql (246B) // 1595862781_add_audio_data.up.sql (246B)
// 1595865249_create_emoji_reactions_table.down.sql (27B) // 1595865249_create_emoji_reactions_table.down.sql (27B)
// 1595865249_create_emoji_reactions_table.up.sql (300B) // 1595865249_create_emoji_reactions_table.up.sql (300B)
// 1596805115_create_group_chat_invitations_table.down.sql (34B)
// 1596805115_create_group_chat_invitations_table.up.sql (231B)
// 1597322655_add_invitation_admin_chat_field.up.sql (54B)
// 1597757544_add_nickname.up.sql (52B) // 1597757544_add_nickname.up.sql (52B)
// doc.go (850B) // doc.go (850B)
@ -448,6 +451,66 @@ func _1595865249_create_emoji_reactions_tableUpSql() (*asset, error) {
return a, nil return a, nil
} }
var __1596805115_create_group_chat_invitations_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\x2f\xca\x2f\x2d\x88\x4f\xce\x48\x2c\x89\xcf\xcc\x2b\xcb\x2c\x49\x2c\xc9\xcc\xcf\x2b\xb6\x06\x04\x00\x00\xff\xff\x82\x66\x9d\x1a\x22\x00\x00\x00")
func _1596805115_create_group_chat_invitations_tableDownSqlBytes() ([]byte, error) {
return bindataRead(
__1596805115_create_group_chat_invitations_tableDownSql,
"1596805115_create_group_chat_invitations_table.down.sql",
)
}
func _1596805115_create_group_chat_invitations_tableDownSql() (*asset, error) {
bytes, err := _1596805115_create_group_chat_invitations_tableDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1596805115_create_group_chat_invitations_table.down.sql", size: 34, mode: os.FileMode(0644), modTime: time.Unix(1599471320, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x5a, 0x17, 0xd8, 0x8d, 0xb3, 0xfe, 0xcb, 0xb6, 0xc0, 0xcb, 0x14, 0x68, 0x8c, 0x5b, 0x18, 0xf8, 0x7d, 0xc9, 0x2c, 0xa6, 0x41, 0xc9, 0x71, 0xeb, 0x3f, 0xc6, 0xa, 0x45, 0xee, 0x5d, 0x2a}}
return a, nil
}
var __1596805115_create_group_chat_invitations_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8d\xc1\x4a\xc4\x30\x14\x45\xf7\x85\xfe\xc3\x5d\x2a\xb8\x70\xef\x2a\xc6\x57\x0c\xc6\xb4\xa4\xaf\xd2\xae\x4a\xa8\xa1\x06\xb5\x91\x26\xf5\xfb\xc5\x96\x61\x18\x98\xed\x3d\x87\x73\xa5\x25\xc1\x04\x16\x8f\x9a\xa0\x2a\x98\x9a\x41\xbd\x6a\xb9\xc5\xbc\xc6\xed\x67\x9c\x3e\x5c\x1e\xc3\xf2\x1b\xb2\xcb\x21\x2e\x09\x37\x65\x01\x84\x77\xbc\x09\x2b\x9f\x85\x45\x63\xd5\xab\xb0\x03\x5e\x68\x40\x6d\x20\x6b\x53\x69\x25\x19\x96\x1a\x2d\x24\xdd\xfd\xeb\x29\x6e\xeb\xe4\xc1\xd4\xf3\xfe\x60\x3a\xad\x77\x70\xd4\xcf\xb1\x0b\xf8\xed\x53\x72\xb3\xbf\x0e\x53\x76\xd9\x43\x19\xc6\x13\x55\xa2\xd3\x8c\xfb\xa3\xf8\x15\xa7\xcf\x7d\x3f\xe9\x65\x71\xfb\xf0\x17\x00\x00\xff\xff\x34\x03\xb2\x2f\xe7\x00\x00\x00")
func _1596805115_create_group_chat_invitations_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__1596805115_create_group_chat_invitations_tableUpSql,
"1596805115_create_group_chat_invitations_table.up.sql",
)
}
func _1596805115_create_group_chat_invitations_tableUpSql() (*asset, error) {
bytes, err := _1596805115_create_group_chat_invitations_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1596805115_create_group_chat_invitations_table.up.sql", size: 231, mode: os.FileMode(0644), modTime: time.Unix(1599471320, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6d, 0xb1, 0x14, 0x6d, 0x54, 0x28, 0x67, 0xc3, 0x23, 0x6a, 0xfc, 0x80, 0xdf, 0x9e, 0x4c, 0x35, 0x36, 0xf, 0xf8, 0xf3, 0x5f, 0xae, 0xad, 0xb, 0xc1, 0x51, 0x8e, 0x17, 0x7, 0xe5, 0x7f, 0x91}}
return a, nil
}
var __1597322655_add_invitation_admin_chat_fieldUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xce\x48\x2c\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xcc\x2b\xcb\x2c\x49\x2c\xc9\xcc\xcf\x8b\x4f\x4c\xc9\xcd\xcc\x53\x08\x73\x0c\x72\xf6\x70\x0c\xb2\x06\x04\x00\x00\xff\xff\x51\xe6\x0d\x74\x36\x00\x00\x00")
func _1597322655_add_invitation_admin_chat_fieldUpSqlBytes() ([]byte, error) {
return bindataRead(
__1597322655_add_invitation_admin_chat_fieldUpSql,
"1597322655_add_invitation_admin_chat_field.up.sql",
)
}
func _1597322655_add_invitation_admin_chat_fieldUpSql() (*asset, error) {
bytes, err := _1597322655_add_invitation_admin_chat_fieldUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1597322655_add_invitation_admin_chat_field.up.sql", size: 54, mode: os.FileMode(0644), modTime: time.Unix(1599471320, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa9, 0x7a, 0xa0, 0xf2, 0xdb, 0x13, 0x91, 0x91, 0xa8, 0x34, 0x1a, 0xa1, 0x49, 0x68, 0xd5, 0xae, 0x2c, 0xd8, 0xd5, 0xea, 0x8f, 0x8c, 0xc7, 0x2, 0x4e, 0x58, 0x2c, 0x3a, 0x14, 0xd4, 0x4f, 0x2c}}
return a, nil
}
var __1597757544_add_nicknameUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\x2b\x49\x4c\x2e\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xc9\x4f\x4e\xcc\x89\xcf\xcb\x4c\xce\xce\x4b\xcc\x4d\x55\x08\x71\x8d\x08\xb1\x06\x04\x00\x00\xff\xff\x54\xf7\xdc\x23\x34\x00\x00\x00") var __1597757544_add_nicknameUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\x2b\x49\x4c\x2e\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xc9\x4f\x4e\xcc\x89\xcf\xcb\x4c\xce\xce\x4b\xcc\x4d\x55\x08\x71\x8d\x08\xb1\x06\x04\x00\x00\xff\xff\x54\xf7\xdc\x23\x34\x00\x00\x00")
func _1597757544_add_nicknameUpSqlBytes() ([]byte, error) { func _1597757544_add_nicknameUpSqlBytes() ([]byte, error) {
@ -463,7 +526,7 @@ func _1597757544_add_nicknameUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1597757544_add_nickname.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1598445725, 0)} info := bindataFileInfo{name: "1597757544_add_nickname.up.sql", size: 52, mode: os.FileMode(0644), modTime: time.Unix(1599471320, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf4, 0xa2, 0x64, 0x50, 0xc5, 0x4, 0xb9, 0x8b, 0xd1, 0x18, 0x9b, 0xc3, 0x91, 0x36, 0x2a, 0x1f, 0xc3, 0x6c, 0x2d, 0x92, 0xf8, 0x5e, 0xff, 0xb1, 0x59, 0x61, 0x2, 0x1c, 0xe1, 0x85, 0x90, 0xa4}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf4, 0xa2, 0x64, 0x50, 0xc5, 0x4, 0xb9, 0x8b, 0xd1, 0x18, 0x9b, 0xc3, 0x91, 0x36, 0x2a, 0x1f, 0xc3, 0x6c, 0x2d, 0x92, 0xf8, 0x5e, 0xff, 0xb1, 0x59, 0x61, 0x2, 0x1c, 0xe1, 0x85, 0x90, 0xa4}}
return a, nil return a, nil
} }
@ -615,6 +678,12 @@ var _bindata = map[string]func() (*asset, error){
"1595865249_create_emoji_reactions_table.up.sql": _1595865249_create_emoji_reactions_tableUpSql, "1595865249_create_emoji_reactions_table.up.sql": _1595865249_create_emoji_reactions_tableUpSql,
"1596805115_create_group_chat_invitations_table.down.sql": _1596805115_create_group_chat_invitations_tableDownSql,
"1596805115_create_group_chat_invitations_table.up.sql": _1596805115_create_group_chat_invitations_tableUpSql,
"1597322655_add_invitation_admin_chat_field.up.sql": _1597322655_add_invitation_admin_chat_fieldUpSql,
"1597757544_add_nickname.up.sql": _1597757544_add_nicknameUpSql, "1597757544_add_nickname.up.sql": _1597757544_add_nicknameUpSql,
"doc.go": docGo, "doc.go": docGo,
@ -679,6 +748,9 @@ var _bintree = &bintree{nil, map[string]*bintree{
"1595862781_add_audio_data.up.sql": &bintree{_1595862781_add_audio_dataUpSql, map[string]*bintree{}}, "1595862781_add_audio_data.up.sql": &bintree{_1595862781_add_audio_dataUpSql, map[string]*bintree{}},
"1595865249_create_emoji_reactions_table.down.sql": &bintree{_1595865249_create_emoji_reactions_tableDownSql, map[string]*bintree{}}, "1595865249_create_emoji_reactions_table.down.sql": &bintree{_1595865249_create_emoji_reactions_tableDownSql, map[string]*bintree{}},
"1595865249_create_emoji_reactions_table.up.sql": &bintree{_1595865249_create_emoji_reactions_tableUpSql, map[string]*bintree{}}, "1595865249_create_emoji_reactions_table.up.sql": &bintree{_1595865249_create_emoji_reactions_tableUpSql, map[string]*bintree{}},
"1596805115_create_group_chat_invitations_table.down.sql": &bintree{_1596805115_create_group_chat_invitations_tableDownSql, map[string]*bintree{}},
"1596805115_create_group_chat_invitations_table.up.sql": &bintree{_1596805115_create_group_chat_invitations_tableUpSql, map[string]*bintree{}},
"1597322655_add_invitation_admin_chat_field.up.sql": &bintree{_1597322655_add_invitation_admin_chat_fieldUpSql, map[string]*bintree{}},
"1597757544_add_nickname.up.sql": &bintree{_1597757544_add_nicknameUpSql, map[string]*bintree{}}, "1597757544_add_nickname.up.sql": &bintree{_1597757544_add_nicknameUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}}, "doc.go": &bintree{docGo, map[string]*bintree{}},
}} }}

View File

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

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS group_chat_invitations (
id VARCHAR PRIMARY KEY ON CONFLICT REPLACE,
source TEXT NOT NULL,
chat_id VARCHAR NOT NULL,
message VARCHAR NOT NULL,
state INT DEFAULT 0,
clock INT NOT NULL
);

View File

@ -0,0 +1 @@
ALTER TABLE chats ADD COLUMN invitation_admin VARCHAR;

View File

@ -122,8 +122,8 @@ func (db sqlitePersistence) saveChat(tx *sql.Tx, chat Chat) error {
} }
// Insert record // Insert record
stmt, err := tx.Prepare(`INSERT INTO chats(id, name, color, active, type, timestamp, deleted_at_clock_value, unviewed_message_count, last_clock_value, last_message, members, membership_updates, muted) stmt, err := tx.Prepare(`INSERT INTO chats(id, name, color, active, type, timestamp, deleted_at_clock_value, unviewed_message_count, last_clock_value, last_message, members, membership_updates, muted, invitation_admin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?)`)
if err != nil { if err != nil {
return err return err
} }
@ -143,6 +143,7 @@ func (db sqlitePersistence) saveChat(tx *sql.Tx, chat Chat) error {
encodedMembers.Bytes(), encodedMembers.Bytes(),
encodedMembershipUpdates.Bytes(), encodedMembershipUpdates.Bytes(),
chat.Muted, chat.Muted,
chat.InvitationAdmin,
) )
if err != nil { if err != nil {
return err return err
@ -201,6 +202,7 @@ func (db sqlitePersistence) chats(tx *sql.Tx) (chats []*Chat, err error) {
chats.members, chats.members,
chats.membership_updates, chats.membership_updates,
chats.muted, chats.muted,
chats.invitation_admin,
contacts.identicon, contacts.identicon,
contacts.alias contacts.alias
FROM chats LEFT JOIN contacts ON chats.id = contacts.id FROM chats LEFT JOIN contacts ON chats.id = contacts.id
@ -215,6 +217,7 @@ func (db sqlitePersistence) chats(tx *sql.Tx) (chats []*Chat, err error) {
var ( var (
alias sql.NullString alias sql.NullString
identicon sql.NullString identicon sql.NullString
invitationAdmin sql.NullString
chat Chat chat Chat
encodedMembers []byte encodedMembers []byte
encodedMembershipUpdates []byte encodedMembershipUpdates []byte
@ -234,13 +237,19 @@ func (db sqlitePersistence) chats(tx *sql.Tx) (chats []*Chat, err error) {
&encodedMembers, &encodedMembers,
&encodedMembershipUpdates, &encodedMembershipUpdates,
&chat.Muted, &chat.Muted,
&invitationAdmin,
&identicon, &identicon,
&alias, &alias,
) )
if err != nil { if err != nil {
return return
} }
if invitationAdmin.Valid {
chat.InvitationAdmin = invitationAdmin.String
}
// Restore members // Restore members
membersDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembers)) membersDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembers))
err = membersDecoder.Decode(&chat.Members) err = membersDecoder.Decode(&chat.Members)
@ -278,6 +287,7 @@ func (db sqlitePersistence) Chat(chatID string) (*Chat, error) {
encodedMembers []byte encodedMembers []byte
encodedMembershipUpdates []byte encodedMembershipUpdates []byte
lastMessageBytes []byte lastMessageBytes []byte
invitationAdmin sql.NullString
) )
err := db.db.QueryRow(` err := db.db.QueryRow(`
@ -294,7 +304,8 @@ func (db sqlitePersistence) Chat(chatID string) (*Chat, error) {
last_message, last_message,
members, members,
membership_updates, membership_updates,
muted muted,
invitation_admin
FROM chats FROM chats
WHERE id = ? WHERE id = ?
`, chatID).Scan(&chat.ID, `, chatID).Scan(&chat.ID,
@ -310,11 +321,15 @@ func (db sqlitePersistence) Chat(chatID string) (*Chat, error) {
&encodedMembers, &encodedMembers,
&encodedMembershipUpdates, &encodedMembershipUpdates,
&chat.Muted, &chat.Muted,
&invitationAdmin,
) )
switch err { switch err {
case sql.ErrNoRows: case sql.ErrNoRows:
return nil, nil return nil, nil
case nil: case nil:
if invitationAdmin.Valid {
chat.InvitationAdmin = invitationAdmin.String
}
// Restore members // Restore members
membersDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembers)) membersDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembers))
err = membersDecoder.Decode(&chat.Members) err = membersDecoder.Decode(&chat.Members)

View File

@ -46,7 +46,7 @@ const (
ApplicationMetadataMessage_PUSH_NOTIFICATION_REQUEST ApplicationMetadataMessage_Type = 20 ApplicationMetadataMessage_PUSH_NOTIFICATION_REQUEST ApplicationMetadataMessage_Type = 20
ApplicationMetadataMessage_PUSH_NOTIFICATION_RESPONSE ApplicationMetadataMessage_Type = 21 ApplicationMetadataMessage_PUSH_NOTIFICATION_RESPONSE ApplicationMetadataMessage_Type = 21
ApplicationMetadataMessage_EMOJI_REACTION ApplicationMetadataMessage_Type = 22 ApplicationMetadataMessage_EMOJI_REACTION ApplicationMetadataMessage_Type = 22
ApplicationMetadataMessage_EMOJI_REACTION_RETRACTION ApplicationMetadataMessage_Type = 23 ApplicationMetadataMessage_GROUP_CHAT_INVITATION ApplicationMetadataMessage_Type = 23
) )
var ApplicationMetadataMessage_Type_name = map[int32]string{ var ApplicationMetadataMessage_Type_name = map[int32]string{
@ -73,7 +73,7 @@ var ApplicationMetadataMessage_Type_name = map[int32]string{
20: "PUSH_NOTIFICATION_REQUEST", 20: "PUSH_NOTIFICATION_REQUEST",
21: "PUSH_NOTIFICATION_RESPONSE", 21: "PUSH_NOTIFICATION_RESPONSE",
22: "EMOJI_REACTION", 22: "EMOJI_REACTION",
23: "EMOJI_REACTION_RETRACTION", 23: "GROUP_CHAT_INVITATION",
} }
var ApplicationMetadataMessage_Type_value = map[string]int32{ var ApplicationMetadataMessage_Type_value = map[string]int32{
@ -100,7 +100,7 @@ var ApplicationMetadataMessage_Type_value = map[string]int32{
"PUSH_NOTIFICATION_REQUEST": 20, "PUSH_NOTIFICATION_REQUEST": 20,
"PUSH_NOTIFICATION_RESPONSE": 21, "PUSH_NOTIFICATION_RESPONSE": 21,
"EMOJI_REACTION": 22, "EMOJI_REACTION": 22,
"EMOJI_REACTION_RETRACTION": 23, "GROUP_CHAT_INVITATION": 23,
} }
func (x ApplicationMetadataMessage_Type) String() string { func (x ApplicationMetadataMessage_Type) String() string {
@ -174,39 +174,41 @@ func init() {
proto.RegisterType((*ApplicationMetadataMessage)(nil), "protobuf.ApplicationMetadataMessage") proto.RegisterType((*ApplicationMetadataMessage)(nil), "protobuf.ApplicationMetadataMessage")
} }
func init() { proto.RegisterFile("application_metadata_message.proto", fileDescriptor_ad09a6406fcf24c7) } func init() {
proto.RegisterFile("application_metadata_message.proto", fileDescriptor_ad09a6406fcf24c7)
}
var fileDescriptor_ad09a6406fcf24c7 = []byte{ var fileDescriptor_ad09a6406fcf24c7 = []byte{
// 486 bytes of a gzipped FileDescriptorProto // 493 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xd1, 0x52, 0xd3, 0x4e, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0x5f, 0x53, 0xd3, 0x4c,
0x14, 0xc6, 0xff, 0x85, 0xd2, 0xc2, 0xa1, 0xff, 0xba, 0x1c, 0xc0, 0x56, 0x10, 0xa8, 0xd5, 0x51, 0x14, 0xc6, 0xdf, 0xf2, 0x9f, 0x03, 0x6f, 0x5d, 0x0e, 0xd4, 0x16, 0x10, 0xa8, 0xd5, 0x51, 0xd4,
0xd4, 0x99, 0x5e, 0xe8, 0xb5, 0x17, 0xcb, 0xe6, 0x40, 0xa3, 0xcd, 0x26, 0xec, 0x6e, 0x74, 0xb8, 0x99, 0x5e, 0xe8, 0xb5, 0x17, 0xcb, 0xe6, 0xd0, 0xae, 0x36, 0x9b, 0xb0, 0xbb, 0xc1, 0xe1, 0x6a,
0xda, 0x09, 0x12, 0x99, 0xce, 0x00, 0xcd, 0xd0, 0x70, 0xd1, 0x67, 0xf5, 0x29, 0x7c, 0x03, 0x27, 0x27, 0x48, 0x64, 0x3a, 0x03, 0x34, 0x43, 0xc3, 0x45, 0x3f, 0xa9, 0x9f, 0xc2, 0xef, 0xe0, 0x24,
0x69, 0x6a, 0xa9, 0x2d, 0x72, 0x95, 0xd9, 0xef, 0xfb, 0x9d, 0x73, 0xe6, 0x7c, 0x9b, 0x85, 0x76, 0x6d, 0x2d, 0xd8, 0x22, 0x57, 0x99, 0x7d, 0x9e, 0xdf, 0x39, 0x67, 0xce, 0xb3, 0x59, 0x68, 0xc4,
0x94, 0x24, 0x57, 0xfd, 0xef, 0x51, 0xda, 0x1f, 0xdc, 0xd8, 0xeb, 0x38, 0x8d, 0x2e, 0xa2, 0x34, 0x69, 0x7a, 0xd5, 0xfd, 0x1e, 0x67, 0xdd, 0xde, 0x8d, 0xbb, 0x4e, 0xb2, 0xf8, 0x22, 0xce, 0x62,
0xb2, 0xd7, 0xf1, 0x70, 0x18, 0x5d, 0xc6, 0x9d, 0xe4, 0x76, 0x90, 0x0e, 0x70, 0x35, 0xff, 0x9c, 0x77, 0x9d, 0xf4, 0xfb, 0xf1, 0x65, 0xd2, 0x4c, 0x6f, 0x7b, 0x59, 0x0f, 0x57, 0x8a, 0xcf, 0xf9,
0xdf, 0xfd, 0x68, 0xff, 0xaa, 0xc0, 0x0e, 0x9f, 0x16, 0x78, 0x05, 0xef, 0x8d, 0x71, 0x7c, 0x0e, 0xdd, 0x8f, 0xc6, 0xaf, 0x25, 0xd8, 0xe1, 0x93, 0x02, 0x7f, 0xc4, 0xfb, 0x43, 0x1c, 0x5f, 0xc0,
0x6b, 0xc3, 0xfe, 0xe5, 0x4d, 0x94, 0xde, 0xdd, 0xc6, 0xcd, 0x52, 0xab, 0x74, 0x58, 0x53, 0x53, 0x6a, 0xbf, 0x7b, 0x79, 0x13, 0x67, 0x77, 0xb7, 0x49, 0xad, 0x54, 0x2f, 0x1d, 0xae, 0xeb, 0x89,
0x01, 0x9b, 0x50, 0x4d, 0xa2, 0xd1, 0xd5, 0x20, 0xba, 0x68, 0x2e, 0xe5, 0xde, 0xe4, 0x88, 0x9f, 0x80, 0x35, 0x58, 0x4e, 0xe3, 0xc1, 0x55, 0x2f, 0xbe, 0xa8, 0xcd, 0x15, 0xde, 0xf8, 0x88, 0x9f,
0xa0, 0x9c, 0x8e, 0x92, 0xb8, 0xb9, 0xdc, 0x2a, 0x1d, 0xd6, 0x3f, 0xbc, 0xed, 0x4c, 0xe6, 0x75, 0x61, 0x21, 0x1b, 0xa4, 0x49, 0x6d, 0xbe, 0x5e, 0x3a, 0x2c, 0x7f, 0x7c, 0xd7, 0x1c, 0xcf, 0x6b,
0x1e, 0x9e, 0xd5, 0x31, 0xa3, 0x24, 0x56, 0x79, 0x59, 0xfb, 0xe7, 0x0a, 0x94, 0xb3, 0x23, 0xae, 0x3e, 0x3e, 0xab, 0x69, 0x07, 0x69, 0xa2, 0x8b, 0xb2, 0xc6, 0xcf, 0x45, 0x58, 0xc8, 0x8f, 0xb8,
0x43, 0x35, 0x94, 0x5f, 0xa4, 0xff, 0x4d, 0xb2, 0xff, 0x90, 0x41, 0x4d, 0x74, 0xb9, 0xb1, 0x1e, 0x06, 0xcb, 0x91, 0xfa, 0xaa, 0x82, 0x6f, 0x8a, 0xfd, 0x87, 0x0c, 0xd6, 0x45, 0x9b, 0x5b, 0xe7,
0x69, 0xcd, 0x4f, 0x88, 0x95, 0x10, 0xa1, 0x2e, 0x7c, 0x69, 0xb8, 0x30, 0x36, 0x0c, 0x1c, 0x6e, 0x93, 0x31, 0xbc, 0x45, 0xac, 0x84, 0x08, 0x65, 0x11, 0x28, 0xcb, 0x85, 0x75, 0x51, 0xe8, 0x71,
0x88, 0x2d, 0xe1, 0x1e, 0x3c, 0xf3, 0xc8, 0x3b, 0x22, 0xa5, 0xbb, 0x6e, 0x50, 0xc8, 0x7f, 0x4a, 0x4b, 0x6c, 0x0e, 0xf7, 0x60, 0xdb, 0x27, 0xff, 0x88, 0xb4, 0x69, 0xcb, 0x70, 0x24, 0xff, 0x29,
0x96, 0x71, 0x1b, 0x36, 0x02, 0xee, 0x2a, 0xeb, 0x4a, 0x6d, 0x78, 0xaf, 0xc7, 0x8d, 0xeb, 0x4b, 0x99, 0xc7, 0x0a, 0x6c, 0x84, 0x5c, 0x6a, 0x27, 0x95, 0xb1, 0xbc, 0xd3, 0xe1, 0x56, 0x06, 0x8a,
0x56, 0xce, 0x64, 0x7d, 0x26, 0xc5, 0xac, 0xbc, 0x82, 0x2f, 0xe1, 0x40, 0xd1, 0x69, 0x48, 0xda, 0x2d, 0xe4, 0xb2, 0x39, 0x53, 0xe2, 0xa1, 0xbc, 0x88, 0xaf, 0xe0, 0x40, 0xd3, 0x49, 0x44, 0xc6,
0x58, 0xee, 0x38, 0x8a, 0xb4, 0xb6, 0xc7, 0xbe, 0xb2, 0x46, 0x71, 0xa9, 0xb9, 0xc8, 0xa1, 0x0a, 0x3a, 0xee, 0x79, 0x9a, 0x8c, 0x71, 0xc7, 0x81, 0x76, 0x56, 0x73, 0x65, 0xb8, 0x28, 0xa0, 0x25,
0xbe, 0x83, 0xd7, 0x5c, 0x08, 0x0a, 0x8c, 0x7d, 0x8c, 0xad, 0xe2, 0x7b, 0x78, 0xe3, 0x90, 0xe8, 0x7c, 0x0f, 0x6f, 0xb8, 0x10, 0x14, 0x5a, 0xf7, 0x14, 0xbb, 0x8c, 0x1f, 0xe0, 0xad, 0x47, 0xa2,
0xb9, 0x92, 0x1e, 0x85, 0x57, 0xb1, 0x01, 0x9b, 0x13, 0xe8, 0xbe, 0xb1, 0x86, 0x5b, 0xc0, 0x34, 0x23, 0x15, 0x3d, 0x09, 0xaf, 0x60, 0x15, 0x36, 0xc7, 0xd0, 0x7d, 0x63, 0x15, 0xb7, 0x80, 0x19,
0x49, 0x67, 0x46, 0x05, 0x3c, 0x80, 0xdd, 0xbf, 0x7b, 0xdf, 0x07, 0xd6, 0xb3, 0x68, 0xe6, 0x96, 0x52, 0xde, 0x03, 0x15, 0xf0, 0x00, 0x76, 0xff, 0xee, 0x7d, 0x1f, 0x58, 0xcb, 0xa3, 0x99, 0x5a,
0xb4, 0x45, 0x80, 0xac, 0xb6, 0xd8, 0xe6, 0x42, 0xf8, 0xa1, 0x34, 0xec, 0x7f, 0x7c, 0x01, 0x7b, 0xd2, 0x8d, 0x02, 0x64, 0xeb, 0xb3, 0x6d, 0x2e, 0x44, 0x10, 0x29, 0xcb, 0xfe, 0xc7, 0x97, 0xb0,
0xf3, 0x76, 0x10, 0x1e, 0xf5, 0x5c, 0x61, 0xb3, 0x7b, 0x61, 0x75, 0xdc, 0x87, 0x9d, 0xc9, 0x7d, 0x37, 0x6d, 0x87, 0xd1, 0x51, 0x47, 0x0a, 0x97, 0xdf, 0x0b, 0x2b, 0xe3, 0x3e, 0xec, 0x8c, 0xef,
0x08, 0xdf, 0x21, 0xcb, 0x9d, 0xaf, 0xa4, 0x8c, 0xab, 0xc9, 0x23, 0x69, 0xd8, 0x13, 0x6c, 0xc3, 0x43, 0x04, 0x1e, 0x39, 0xee, 0x9d, 0x92, 0xb6, 0xd2, 0x90, 0x4f, 0xca, 0xb2, 0x67, 0xd8, 0x80,
0x7e, 0x10, 0xea, 0xae, 0x95, 0xbe, 0x71, 0x8f, 0x5d, 0x31, 0x6e, 0xa1, 0xe8, 0xc4, 0xd5, 0x46, 0xfd, 0x30, 0x32, 0x6d, 0xa7, 0x02, 0x2b, 0x8f, 0xa5, 0x18, 0xb6, 0xd0, 0xd4, 0x92, 0xc6, 0xea,
0x8d, 0x23, 0x67, 0x59, 0x42, 0xff, 0x66, 0xac, 0x22, 0x1d, 0xf8, 0x52, 0x13, 0xdb, 0xc0, 0x5d, 0x61, 0xe4, 0x2c, 0x4f, 0xe8, 0xdf, 0x8c, 0xd3, 0x64, 0xc2, 0x40, 0x19, 0x62, 0x1b, 0xb8, 0x0b,
0x68, 0xcc, 0xc3, 0xa7, 0x21, 0xa9, 0x33, 0x86, 0xf8, 0x0a, 0x5a, 0x0f, 0x98, 0xd3, 0x16, 0x9b, 0xd5, 0x69, 0xf8, 0x24, 0x22, 0x7d, 0xc6, 0x10, 0x5f, 0x43, 0xfd, 0x11, 0x73, 0xd2, 0x62, 0x33,
0xd9, 0xd6, 0x8b, 0xe6, 0xe5, 0xf9, 0xb1, 0xad, 0x6c, 0xa5, 0x45, 0x76, 0x51, 0xbe, 0x9d, 0xfd, 0xdf, 0x7a, 0xd6, 0xbc, 0x22, 0x3f, 0xb6, 0x95, 0xaf, 0x34, 0xcb, 0x1e, 0x95, 0x57, 0xf2, 0x5f,
0x82, 0xe4, 0xf9, 0x9f, 0x5d, 0xab, 0xa8, 0xc8, 0xf9, 0x69, 0xd6, 0x72, 0x56, 0xb3, 0x8a, 0x8c, 0x90, 0xfc, 0xe0, 0x8b, 0x74, 0x9a, 0x46, 0x39, 0x3f, 0xc7, 0x6d, 0xa8, 0xb4, 0x74, 0x10, 0x85,
0x2a, 0xec, 0xc6, 0x79, 0x25, 0x7f, 0x0d, 0x1f, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x79, 0x47, 0x45, 0x2c, 0x4e, 0xaa, 0x53, 0x69, 0x87, 0xdb, 0x55, 0xcf, 0x97, 0x8a, 0x97, 0xf0, 0xe9, 0x77,
0x6f, 0x0d, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x4d, 0x74, 0xca, 0xa6, 0x03, 0x00, 0x00,
} }

View File

@ -35,6 +35,6 @@ message ApplicationMetadataMessage {
PUSH_NOTIFICATION_REQUEST = 20; PUSH_NOTIFICATION_REQUEST = 20;
PUSH_NOTIFICATION_RESPONSE = 21; PUSH_NOTIFICATION_RESPONSE = 21;
EMOJI_REACTION = 22; EMOJI_REACTION = 22;
EMOJI_REACTION_RETRACTION = 23; GROUP_CHAT_INVITATION = 23;
} }
} }

View File

@ -0,0 +1,147 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: group_chat_invitation.proto
package protobuf
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type GroupChatInvitation_State int32
const (
GroupChatInvitation_UNKNOWN GroupChatInvitation_State = 0
GroupChatInvitation_REQUEST GroupChatInvitation_State = 1
GroupChatInvitation_REJECTED GroupChatInvitation_State = 2
GroupChatInvitation_APPROVED GroupChatInvitation_State = 3
)
var GroupChatInvitation_State_name = map[int32]string{
0: "UNKNOWN",
1: "REQUEST",
2: "REJECTED",
3: "APPROVED",
}
var GroupChatInvitation_State_value = map[string]int32{
"UNKNOWN": 0,
"REQUEST": 1,
"REJECTED": 2,
"APPROVED": 3,
}
func (x GroupChatInvitation_State) String() string {
return proto.EnumName(GroupChatInvitation_State_name, int32(x))
}
func (GroupChatInvitation_State) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_a6a73333de6a8ebe, []int{0, 0}
}
type GroupChatInvitation struct {
// clock Lamport timestamp of the chat message
Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"`
// chat_id the ID of the private group chat the message belongs to, for query efficiency the chat_id is stored in the db even though the
// target message also stores the chat_id
ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
IntroductionMessage string `protobuf:"bytes,3,opt,name=introduction_message,json=introductionMessage,proto3" json:"introduction_message,omitempty"`
// state of invitation
State GroupChatInvitation_State `protobuf:"varint,4,opt,name=state,proto3,enum=protobuf.GroupChatInvitation_State" json:"state,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GroupChatInvitation) Reset() { *m = GroupChatInvitation{} }
func (m *GroupChatInvitation) String() string { return proto.CompactTextString(m) }
func (*GroupChatInvitation) ProtoMessage() {}
func (*GroupChatInvitation) Descriptor() ([]byte, []int) {
return fileDescriptor_a6a73333de6a8ebe, []int{0}
}
func (m *GroupChatInvitation) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GroupChatInvitation.Unmarshal(m, b)
}
func (m *GroupChatInvitation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GroupChatInvitation.Marshal(b, m, deterministic)
}
func (m *GroupChatInvitation) XXX_Merge(src proto.Message) {
xxx_messageInfo_GroupChatInvitation.Merge(m, src)
}
func (m *GroupChatInvitation) XXX_Size() int {
return xxx_messageInfo_GroupChatInvitation.Size(m)
}
func (m *GroupChatInvitation) XXX_DiscardUnknown() {
xxx_messageInfo_GroupChatInvitation.DiscardUnknown(m)
}
var xxx_messageInfo_GroupChatInvitation proto.InternalMessageInfo
func (m *GroupChatInvitation) GetClock() uint64 {
if m != nil {
return m.Clock
}
return 0
}
func (m *GroupChatInvitation) GetChatId() string {
if m != nil {
return m.ChatId
}
return ""
}
func (m *GroupChatInvitation) GetIntroductionMessage() string {
if m != nil {
return m.IntroductionMessage
}
return ""
}
func (m *GroupChatInvitation) GetState() GroupChatInvitation_State {
if m != nil {
return m.State
}
return GroupChatInvitation_UNKNOWN
}
func init() {
proto.RegisterEnum("protobuf.GroupChatInvitation_State", GroupChatInvitation_State_name, GroupChatInvitation_State_value)
proto.RegisterType((*GroupChatInvitation)(nil), "protobuf.GroupChatInvitation")
}
func init() {
proto.RegisterFile("group_chat_invitation.proto", fileDescriptor_a6a73333de6a8ebe)
}
var fileDescriptor_a6a73333de6a8ebe = []byte{
// 234 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4e, 0x2f, 0xca, 0x2f,
0x2d, 0x88, 0x4f, 0xce, 0x48, 0x2c, 0x89, 0xcf, 0xcc, 0x2b, 0xcb, 0x2c, 0x49, 0x2c, 0xc9, 0xcc,
0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x53, 0x49, 0xa5, 0x69, 0x4a, 0x1f,
0x19, 0xb9, 0x84, 0xdd, 0x41, 0x2a, 0x9d, 0x33, 0x12, 0x4b, 0x3c, 0xe1, 0xea, 0x84, 0x44, 0xb8,
0x58, 0x93, 0x73, 0xf2, 0x93, 0xb3, 0x25, 0x18, 0x15, 0x18, 0x35, 0x58, 0x82, 0x20, 0x1c, 0x21,
0x71, 0x2e, 0x76, 0x88, 0x81, 0x29, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x6c, 0x20, 0xae,
0x67, 0x8a, 0x90, 0x21, 0x97, 0x48, 0x66, 0x5e, 0x49, 0x51, 0x7e, 0x4a, 0x69, 0x32, 0x48, 0x7b,
0x7c, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0xaa, 0x04, 0x33, 0x58, 0x95, 0x30, 0xb2, 0x9c, 0x2f,
0x44, 0x4a, 0xc8, 0x92, 0x8b, 0xb5, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0x82, 0x45, 0x81, 0x51, 0x83,
0xcf, 0x48, 0x59, 0x0f, 0xe6, 0x26, 0x3d, 0x2c, 0xee, 0xd1, 0x0b, 0x06, 0x29, 0x0d, 0x82, 0xe8,
0x50, 0xb2, 0xe5, 0x62, 0x05, 0xf3, 0x85, 0xb8, 0xb9, 0xd8, 0x43, 0xfd, 0xbc, 0xfd, 0xfc, 0xc3,
0xfd, 0x04, 0x18, 0x40, 0x9c, 0x20, 0xd7, 0xc0, 0x50, 0xd7, 0xe0, 0x10, 0x01, 0x46, 0x21, 0x1e,
0x2e, 0x8e, 0x20, 0x57, 0x2f, 0x57, 0xe7, 0x10, 0x57, 0x17, 0x01, 0x26, 0x10, 0xcf, 0x31, 0x20,
0x20, 0xc8, 0x3f, 0xcc, 0xd5, 0x45, 0x80, 0x39, 0x89, 0x0d, 0x6c, 0x93, 0x31, 0x20, 0x00, 0x00,
0xff, 0xff, 0x9c, 0xc5, 0x9c, 0xd5, 0x23, 0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,25 @@
syntax = "proto3";
package protobuf;
message GroupChatInvitation {
// clock Lamport timestamp of the chat message
uint64 clock = 1;
// chat_id the ID of the private group chat the message belongs to, for query efficiency the chat_id is stored in the db even though the
// target message also stores the chat_id
string chat_id = 2;
string introduction_message = 3;
// state of invitation
State state = 4;
enum State {
UNKNOWN = 0;
REQUEST = 1;
REJECTED = 2;
APPROVED = 3;
}
}

View File

@ -4,7 +4,7 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
) )
//go:generate protoc --go_out=. ./chat_message.proto ./application_metadata_message.proto ./membership_update_message.proto ./command.proto ./contact.proto ./pairing.proto ./push_notifications.proto ./emoji_reaction.proto ./enums.proto //go:generate protoc --go_out=. ./chat_message.proto ./application_metadata_message.proto ./membership_update_message.proto ./command.proto ./contact.proto ./pairing.proto ./push_notifications.proto ./emoji_reaction.proto ./enums.proto ./group_chat_invitation.proto
func Unmarshal(payload []byte) (*ApplicationMetadataMessage, error) { func Unmarshal(payload []byte) (*ApplicationMetadataMessage, error) {
var message ApplicationMetadataMessage var message ApplicationMetadataMessage

View File

@ -240,6 +240,8 @@ func (m *StatusMessage) HandleApplication() error {
return m.unmarshalProtobufData(new(protobuf.PushNotificationResponse)) return m.unmarshalProtobufData(new(protobuf.PushNotificationResponse))
case protobuf.ApplicationMetadataMessage_EMOJI_REACTION: case protobuf.ApplicationMetadataMessage_EMOJI_REACTION:
return m.unmarshalProtobufData(new(protobuf.EmojiReaction)) return m.unmarshalProtobufData(new(protobuf.EmojiReaction))
case protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION:
return m.unmarshalProtobufData(new(protobuf.GroupChatInvitation))
case protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION: case protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION:
// This message is a bit different as it's encrypted, so we pass it straight through // This message is a bit different as it's encrypted, so we pass it straight through
v := reflect.ValueOf(m.DecryptedPayload) v := reflect.ValueOf(m.DecryptedPayload)

View File

@ -213,6 +213,10 @@ func (api *PublicAPI) CreateGroupChatWithMembers(ctx Context, name string, membe
return api.service.messenger.CreateGroupChatWithMembers(ctx, name, members) return api.service.messenger.CreateGroupChatWithMembers(ctx, name, members)
} }
func (api *PublicAPI) CreateGroupChatFromInvitation(name string, chatID string, adminPK string) (*protocol.MessengerResponse, error) {
return api.service.messenger.CreateGroupChatFromInvitation(name, chatID, adminPK)
}
func (api *PublicAPI) AddMembersToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { func (api *PublicAPI) AddMembersToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) {
return api.service.messenger.AddMembersToGroupChat(ctx, chatID, members) return api.service.messenger.AddMembersToGroupChat(ctx, chatID, members)
} }
@ -233,6 +237,18 @@ func (api *PublicAPI) ChangeGroupChatName(ctx Context, chatID string, name strin
return api.service.messenger.ChangeGroupChatName(ctx, chatID, name) return api.service.messenger.ChangeGroupChatName(ctx, chatID, name)
} }
func (api *PublicAPI) SendGroupChatInvitationRequest(ctx Context, chatID string, adminPK string, message string) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendGroupChatInvitationRequest(ctx, chatID, adminPK, message)
}
func (api *PublicAPI) GetGroupChatInvitations() ([]*protocol.GroupChatInvitation, error) {
return api.service.messenger.GetGroupChatInvitations()
}
func (api *PublicAPI) SendGroupChatInvitationRejection(ctx Context, invitationRequestID string) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendGroupChatInvitationRejection(ctx, invitationRequestID)
}
func (api *PublicAPI) LoadFilters(parent context.Context, chats []*transport.Filter) ([]*transport.Filter, error) { func (api *PublicAPI) LoadFilters(parent context.Context, chats []*transport.Filter) ([]*transport.Filter, error) {
return api.service.messenger.LoadFilters(chats) return api.service.messenger.LoadFilters(chats)
} }