Update channel member notify props to use native JSON (#18114)
* Update channel member notify props to use native JSON Created a new store method that patches the notify props field. https://community-daily.mattermost.com/plugins/focalboard/workspace/zyoahc9uapdn3xdptac6jb69ic?id=285b80a3-257d-41f6-8cf4-ed80ca9d92e5&v=495cdb4d-c13a-4992-8eb9-80cfee2819a4&c=91d08676-4a0e-4f02-8dce-d24d4fc56449 ```release-note NONE ``` * cleanup ```release-note NONE ``` * forgot to commit ```release-note NONE ``` * Fix edge case ```release-note NONE ``` * address review comments ```release-note NONE ``` * fix incorrect order ```release-note NONE ``` * Address review comments ```release-note NONE ``` Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c4c1fda128
Коммит
7bbcf86531
@@ -1632,6 +1632,52 @@ func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) (*model.Chann
|
||||
return updatedMembers[0], nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) UpdateMemberNotifyProps(channelID, userID string, props map[string]string) (*model.ChannelMember, error) {
|
||||
tx, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "begin_transaction")
|
||||
}
|
||||
defer finalizeTransaction(tx)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
_, err = tx.Exec(`UPDATE channelmembers
|
||||
SET notifyprops = notifyprops || $1::jsonb
|
||||
WHERE userid=$2 AND channelid=$3`, model.MapToJson(props), userID, channelID)
|
||||
} else {
|
||||
// It's difficult to construct a SQL query for MySQL
|
||||
// to handle a case of empty map. So we just ignore it.
|
||||
if len(props) > 0 {
|
||||
// unpack the keys and values to pass to MySQL.
|
||||
args, argString := constructMySQLJSONArgs(props)
|
||||
args = append(args, userID, channelID)
|
||||
|
||||
// Example: UPDATE ChannelMembers
|
||||
// SET NotifyProps = JSON_SET(NotifyProps, '$.mark_unread', '"yes"' [, ...])
|
||||
// WHERE ...
|
||||
_, err = tx.Exec(`UPDATE ChannelMembers
|
||||
SET NotifyProps = JSON_SET(NotifyProps, `+argString+`)
|
||||
WHERE UserId=? AND ChannelId=?`, args...)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update ChannelMember with channelID=%s and userID=%s", channelID, userID)
|
||||
}
|
||||
|
||||
var dbMember channelMemberWithSchemeRoles
|
||||
if err2 := tx.SelectOne(&dbMember, ChannelMembersWithSchemeSelectQuery+"WHERE ChannelMembers.ChannelId = :ChannelId AND ChannelMembers.UserId = :UserId", map[string]interface{}{"ChannelId": channelID, "UserId": userID}); err2 != nil {
|
||||
if err2 == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("ChannelMember", fmt.Sprintf("channelId=%s, userId=%s", channelID, userID))
|
||||
}
|
||||
return nil, errors.Wrapf(err2, "failed to get ChannelMember with channelId=%s and userId=%s", channelID, userID)
|
||||
}
|
||||
|
||||
if err2 := tx.Commit(); err2 != nil {
|
||||
return nil, errors.Wrap(err2, "commit_transaction")
|
||||
}
|
||||
|
||||
return dbMember.ToModel(), err
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetMembers(channelId string, offset, limit int) (model.ChannelMembers, error) {
|
||||
var dbMembers channelMemberWithSchemeRolesList
|
||||
_, err := s.GetReplica().Select(&dbMembers, ChannelMembersWithSchemeSelectQuery+"WHERE ChannelId = :ChannelId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Limit": limit, "Offset": offset})
|
||||
|
||||
@@ -89,3 +89,30 @@ func isQuotedWord(s string) bool {
|
||||
|
||||
return s[0] == '"' && s[len(s)-1] == '"'
|
||||
}
|
||||
|
||||
// constructMySQLJSONArgs returns the arg list to pass to a query along with
|
||||
// the string of placeholders which is needed to be to the JSON_SET function.
|
||||
// Use this function in this way:
|
||||
// UPDATE Table
|
||||
// SET Col = JSON_SET(Col, `+argString+`)
|
||||
// WHERE Id=?`, args...)
|
||||
// after appending the Id param to the args slice.
|
||||
func constructMySQLJSONArgs(props map[string]string) ([]interface{}, string) {
|
||||
if len(props) == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// Unpack the keys and values to pass to MySQL.
|
||||
args := make([]interface{}, 0, len(props))
|
||||
for k, v := range props {
|
||||
args = append(args, "$."+k)
|
||||
args = append(args, v)
|
||||
}
|
||||
|
||||
// We calculate the number of ? to set in the query string.
|
||||
argString := strings.Repeat("?, ", len(props)*2)
|
||||
// Strip off the trailing comma.
|
||||
argString = strings.TrimSuffix(strings.TrimSpace(argString), ",")
|
||||
|
||||
return args, argString
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package sqlstore
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -103,3 +104,32 @@ func TestRemoveNonAlphaNumericUnquotedTerms(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMySQLJSONArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
props map[string]string
|
||||
args []interface{}
|
||||
argString string
|
||||
}{
|
||||
{
|
||||
props: map[string]string{
|
||||
"desktop": "linux",
|
||||
"mobile": "android",
|
||||
"notify": "always",
|
||||
},
|
||||
args: []interface{}{"$.desktop", "linux", "$.mobile", "android", "$.notify", "always"},
|
||||
argString: "?, ?, ?, ?, ?, ?",
|
||||
},
|
||||
{
|
||||
props: map[string]string{},
|
||||
args: nil,
|
||||
argString: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
args, argString := constructMySQLJSONArgs(test.props)
|
||||
assert.ElementsMatch(t, test.args, args)
|
||||
assert.Equal(t, test.argString, argString)
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user