Refactor channel-members repo
This commit is contained in:
@@ -2,8 +2,6 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -57,10 +55,6 @@ func (r channel) table() string {
|
||||
return "messaging_channel"
|
||||
}
|
||||
|
||||
func (r channel) tableMember() string {
|
||||
return "messaging_channel_member"
|
||||
}
|
||||
|
||||
func (r channel) columns() []string {
|
||||
return []string{
|
||||
"c.id",
|
||||
@@ -91,29 +85,10 @@ func (r channel) FindByID(ID uint64) (*types.Channel, error) {
|
||||
|
||||
// FindByMemberSet searches for channel (group!) with exactly the same membership structure
|
||||
func (r channel) FindByMemberSet(memberIDs ...uint64) (*types.Channel, error) {
|
||||
// Make sure members are sorted
|
||||
sort.Slice(memberIDs, func(i, j int) bool {
|
||||
return memberIDs[i] < memberIDs[j]
|
||||
})
|
||||
|
||||
// Concatentating members fore
|
||||
membersConcat := ""
|
||||
for i := range memberIDs {
|
||||
// Don't panic, we're adding , in the SQL as well
|
||||
membersConcat += strconv.FormatUint(memberIDs[i], 10) + ","
|
||||
}
|
||||
|
||||
return r.findOneBy(
|
||||
squirrel.And{
|
||||
squirrel.Eq{"type": types.ChannelTypeGroup},
|
||||
squirrel.
|
||||
Select("rel_channel").
|
||||
From(r.tableMember()).
|
||||
GroupBy("rel_channel").
|
||||
Having(squirrel.Eq{
|
||||
"COUNT(*)": len(memberIDs),
|
||||
"CONCAT(GROUP_CONCAT(rel_user ORDER BY 1 ASC SEPARATOR ','),',')": membersConcat,
|
||||
}),
|
||||
squirrel.ConcatExpr("c.id IN (", (channelMember{}).queryExactMembers(memberIDs...), ")"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -145,6 +120,8 @@ func (r channel) Find(filter types.ChannelFilter) (set types.ChannelSet, f types
|
||||
|
||||
query := r.query()
|
||||
|
||||
query = query.Where(squirrel.Eq{"c.archived_at": nil})
|
||||
|
||||
if !f.IncludeDeleted {
|
||||
query = query.Where(squirrel.Eq{"c.deleted_at": nil})
|
||||
}
|
||||
@@ -157,10 +134,7 @@ func (r channel) Find(filter types.ChannelFilter) (set types.ChannelSet, f types
|
||||
if f.CurrentUserID > 0 {
|
||||
query = query.Where(squirrel.Or{
|
||||
squirrel.Eq{"c.type": types.ChannelTypePublic},
|
||||
squirrel.ConcatExpr("c.id IN (", squirrel.
|
||||
Select("rel_channel").
|
||||
From(r.tableMember()).
|
||||
Where(squirrel.Eq{"rel_user": f.CurrentUserID}), ")"),
|
||||
squirrel.ConcatExpr("c.id IN (", (channelMember{}).queryAnyMember(f.CurrentUserID), ")"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/messaging/types"
|
||||
@@ -14,14 +17,11 @@ type (
|
||||
ChannelMemberRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) ChannelMemberRepository
|
||||
|
||||
Find(filter *types.ChannelMemberFilter) (types.ChannelMemberSet, error)
|
||||
Find(filter types.ChannelMemberFilter) (types.ChannelMemberSet, error)
|
||||
|
||||
Create(mod *types.ChannelMember) (*types.ChannelMember, error)
|
||||
Update(mod *types.ChannelMember) (*types.ChannelMember, error)
|
||||
Delete(channelID, userID uint64) error
|
||||
|
||||
CountMemberships(userID uint64) (c int, err error)
|
||||
ChangeMembership(userID, target uint64) error
|
||||
}
|
||||
|
||||
channelMember struct {
|
||||
@@ -29,28 +29,6 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// subquery that filters out all channels that current user has access to as a member
|
||||
// or via channel type (public channels)
|
||||
sqlChannelAccess = ` (
|
||||
SELECT id
|
||||
FROM messaging_channel c
|
||||
LEFT OUTER JOIN messaging_channel_member AS m ON (c.id = m.rel_channel)
|
||||
WHERE rel_user = ?
|
||||
UNION
|
||||
SELECT id
|
||||
FROM messaging_channel c
|
||||
WHERE c.type = ?
|
||||
)`
|
||||
|
||||
// Fetching channel members of all channels a specific user has access to
|
||||
sqlChannelMemberSelect = `SELECT m.*
|
||||
FROM messaging_channel_member AS m
|
||||
INNER JOIN messaging_channel AS c ON (m.rel_channel = c.id)
|
||||
WHERE c.archived_at IS NULL
|
||||
AND c.deleted_at IS NULL`
|
||||
)
|
||||
|
||||
// ChannelMember creates new instance of channel member repository
|
||||
func ChannelMember(ctx context.Context, db *factory.DB) ChannelMemberRepository {
|
||||
return (&channelMember{}).With(ctx, db)
|
||||
@@ -63,35 +41,81 @@ func (r *channelMember) With(ctx context.Context, db *factory.DB) ChannelMemberR
|
||||
}
|
||||
}
|
||||
|
||||
// FindMembers fetches membership info
|
||||
func (r channelMember) table() string {
|
||||
return "messaging_channel_member"
|
||||
}
|
||||
|
||||
func (r channelMember) columns() []string {
|
||||
return []string{
|
||||
"cm.rel_channel",
|
||||
"cm.rel_user",
|
||||
"cm.type",
|
||||
"cm.flag",
|
||||
"cm.created_at",
|
||||
"cm.updated_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (r channelMember) query() squirrel.SelectBuilder {
|
||||
return squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.table() + " AS cm")
|
||||
}
|
||||
|
||||
// Finds channel ID(s) with any of the members
|
||||
//
|
||||
// If channelID > 0 it returns members of a specific channel
|
||||
// If userID > 0 it returns members of all channels this user is member of
|
||||
func (r *channelMember) Find(filter *types.ChannelMemberFilter) (types.ChannelMemberSet, error) {
|
||||
params := make([]interface{}, 0)
|
||||
mm := types.ChannelMemberSet{}
|
||||
// Builds a (sub)query that returns list of channel IDs at least one of the members
|
||||
//
|
||||
func (r channelMember) queryAnyMember(memberIDs ...uint64) squirrel.SelectBuilder {
|
||||
return squirrel.
|
||||
Select("cm.rel_channel").
|
||||
From(r.table() + " AS cm").
|
||||
Where(squirrel.Eq{"cm.rel_user": memberIDs})
|
||||
}
|
||||
|
||||
sql := sqlChannelMemberSelect
|
||||
|
||||
if filter != nil {
|
||||
if filter.ComembersOf > 0 {
|
||||
// scope: only channel we have access to
|
||||
sql += " AND m.rel_channel IN " + sqlChannelAccess
|
||||
params = append(params, filter.ComembersOf, types.ChannelTypePublic)
|
||||
}
|
||||
|
||||
if filter.MemberID > 0 {
|
||||
sql += " AND m.rel_user = ?"
|
||||
params = append(params, filter.MemberID)
|
||||
}
|
||||
|
||||
if filter.ChannelID > 0 {
|
||||
sql += " AND m.rel_channel = ?"
|
||||
params = append(params, filter.ChannelID)
|
||||
}
|
||||
// Finds channel ID(s) with exact membership
|
||||
//
|
||||
// Builds a (sub)query that returns list of channel IDs that have this exact membership
|
||||
//
|
||||
func (r channelMember) queryExactMembers(memberIDs ...uint64) squirrel.SelectBuilder {
|
||||
if len(memberIDs) == 0 {
|
||||
return squirrel.
|
||||
Select("null")
|
||||
}
|
||||
|
||||
return mm, r.db().Select(&mm, sql, params...)
|
||||
// Make sure members are sorted
|
||||
sort.Slice(memberIDs, func(i, j int) bool {
|
||||
return memberIDs[i] < memberIDs[j]
|
||||
})
|
||||
|
||||
// Concatentating members fore
|
||||
membersConcat := ""
|
||||
for i := range memberIDs {
|
||||
// Don't panic, we're adding , in the SQL as well
|
||||
membersConcat += strconv.FormatUint(memberIDs[i], 10) + ","
|
||||
}
|
||||
|
||||
return r.queryAnyMember(memberIDs...).
|
||||
GroupBy("cm.rel_channel").
|
||||
Having(squirrel.Eq{
|
||||
"COUNT(*)": len(memberIDs),
|
||||
"CONCAT(GROUP_CONCAT(cm.rel_user ORDER BY 1 ASC SEPARATOR ','),',')": membersConcat,
|
||||
})
|
||||
}
|
||||
|
||||
// Find fetches membership info
|
||||
func (r *channelMember) Find(filter types.ChannelMemberFilter) (set types.ChannelMemberSet, err error) {
|
||||
query := r.query()
|
||||
|
||||
if len(filter.MemberID) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cm.rel_user": filter.MemberID})
|
||||
}
|
||||
|
||||
if len(filter.ChannelID) > 0 {
|
||||
query = query.Where(squirrel.Eq{"cm.rel_channel": filter.ChannelID})
|
||||
}
|
||||
|
||||
return set, rh.FetchAll(r.db(), query, &set)
|
||||
}
|
||||
|
||||
// Create adds channel membership record
|
||||
@@ -116,26 +140,3 @@ func (r *channelMember) Delete(channelID, userID uint64) error {
|
||||
sql := `DELETE FROM messaging_channel_member WHERE rel_channel = ? AND rel_user = ?`
|
||||
return exec(r.db().Exec(sql, channelID, userID))
|
||||
}
|
||||
|
||||
func (r *channelMember) CountMemberships(userID uint64) (c int, err error) {
|
||||
return c, r.db().Get(&c,
|
||||
"SELECT COUNT(*) FROM messaging_channel_member WHERE rel_user = ?",
|
||||
userID)
|
||||
}
|
||||
|
||||
func (r *channelMember) ChangeMembership(userID, target uint64) (err error) {
|
||||
// Remove dups
|
||||
// with an ugly mysql workaround
|
||||
_, err = r.db().Exec(
|
||||
"DELETE FROM messaging_channel_member WHERE rel_user = ? "+
|
||||
"AND rel_channel IN (SELECT rel_channel FROM (SELECT * FROM messaging_channel_member) AS workaround WHERE rel_user = ?)",
|
||||
userID,
|
||||
target)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.db().Exec("UPDATE messaging_channel_member SET rel_user = ? WHERE rel_user = ?", target, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -42,6 +42,19 @@ type (
|
||||
const (
|
||||
MESSAGES_MAX_LIMIT = 100
|
||||
|
||||
// subquery that filters out all channels that current user has access to as a member
|
||||
// or via channel type (public channels)
|
||||
sqlChannelAccess = ` (
|
||||
SELECT id
|
||||
FROM messaging_channel c
|
||||
LEFT OUTER JOIN messaging_channel_member AS m ON (c.id = m.rel_channel)
|
||||
WHERE rel_user = ?
|
||||
UNION
|
||||
SELECT id
|
||||
FROM messaging_channel c
|
||||
WHERE c.type = ?
|
||||
)`
|
||||
|
||||
sqlMessageColumns = "id, " +
|
||||
"COALESCE(type,'') AS type, " +
|
||||
"message, " +
|
||||
|
||||
@@ -159,7 +159,8 @@ func (svc *channel) preloadMembers(cc types.ChannelSet) (err error) {
|
||||
mm types.ChannelMemberSet
|
||||
)
|
||||
|
||||
if mm, err = svc.cmember.Find(&types.ChannelMemberFilter{ComembersOf: userID}); err != nil {
|
||||
// Load membership info of all channels
|
||||
if mm, err = svc.cmember.Find(types.ChannelMemberFilterChannels(cc.IDs()...)); err != nil {
|
||||
return
|
||||
} else {
|
||||
err = cc.Walk(func(ch *types.Channel) error {
|
||||
@@ -216,7 +217,7 @@ func (svc *channel) FindMembers(channelID uint64) (out types.ChannelMemberSet, e
|
||||
return
|
||||
}
|
||||
|
||||
out, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: channelID})
|
||||
out, err = svc.cmember.Find(types.ChannelMemberFilterChannels(channelID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -549,7 +550,7 @@ func (svc *channel) SetFlag(ID uint64, flag types.ChannelMembershipFlag) (ch *ty
|
||||
return
|
||||
}
|
||||
|
||||
if members, err := svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: userID}); err != nil {
|
||||
if members, err := svc.cmember.Find(types.ChannelMemberFilter{ChannelID: []uint64{ch.ID}, MemberID: []uint64{userID}}); err != nil {
|
||||
return err
|
||||
} else if len(members) == 1 {
|
||||
membership = members[0]
|
||||
@@ -671,7 +672,7 @@ func (svc *channel) InviteUser(channelID uint64, memberIDs ...uint64) (out types
|
||||
}
|
||||
|
||||
return out, svc.db.Transaction(func() (err error) {
|
||||
if existing, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: channelID}); err != nil {
|
||||
if existing, err = svc.cmember.Find(types.ChannelMemberFilterChannels(channelID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -729,7 +730,7 @@ func (svc *channel) AddMember(channelID uint64, memberIDs ...uint64) (out types.
|
||||
}
|
||||
|
||||
return out, svc.db.Transaction(func() (err error) {
|
||||
if existing, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: channelID}); err != nil {
|
||||
if existing, err = svc.cmember.Find(types.ChannelMemberFilterChannels(channelID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -819,7 +820,7 @@ func (svc *channel) DeleteMember(channelID uint64, memberIDs ...uint64) (err err
|
||||
}
|
||||
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
if existing, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: channelID}); err != nil {
|
||||
if existing, err = svc.cmember.Find(types.ChannelMemberFilterChannels(channelID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -882,7 +883,7 @@ func (svc *channel) sendChannelEvent(ch *types.Channel) (err error) {
|
||||
|
||||
// Preload members, if needed
|
||||
if len(ch.Members) == 0 || ch.Member == nil {
|
||||
if mm, err := svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: ch.ID}); err != nil {
|
||||
if mm, err := svc.cmember.Find(types.ChannelMemberFilterChannels(ch.ID)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ch.Members = mm.AllMemberIDs()
|
||||
|
||||
@@ -247,7 +247,7 @@ func (svc message) Create(in *types.Message) (m *types.Message, err error) {
|
||||
//
|
||||
// reset unreads for all members
|
||||
var mm types.ChannelMemberSet
|
||||
mm, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: original.ChannelID})
|
||||
mm, err = svc.cmember.Find(types.ChannelMemberFilterChannels(original.ChannelID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -910,7 +910,7 @@ func (svc message) findChannelByID(channelID uint64) (ch *types.Channel, err err
|
||||
|
||||
if ch, err = svc.channel.FindByID(channelID); err != nil {
|
||||
return nil, err
|
||||
} else if mm, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: ch.ID}); err != nil {
|
||||
} else if mm, err = svc.cmember.Find(types.ChannelMemberFilterChannels(ch.ID)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ch.Members = mm.AllMemberIDs()
|
||||
|
||||
@@ -18,9 +18,8 @@ type (
|
||||
}
|
||||
|
||||
ChannelMemberFilter struct {
|
||||
ComembersOf uint64
|
||||
ChannelID uint64
|
||||
MemberID uint64
|
||||
ChannelID []uint64
|
||||
MemberID []uint64
|
||||
}
|
||||
|
||||
ChannelMembershipType string
|
||||
@@ -37,3 +36,8 @@ const (
|
||||
ChannelMembershipFlagIgnored ChannelMembershipFlag = "ignored"
|
||||
ChannelMembershipFlagNone ChannelMembershipFlag = ""
|
||||
)
|
||||
|
||||
// ChannelMemberFilterChannels helper func for building channel member filter with list of channels
|
||||
func ChannelMemberFilterChannels(ID ...uint64) ChannelMemberFilter {
|
||||
return ChannelMemberFilter{ChannelID: ID}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ func TestChannelSetFlag(t *testing.T) {
|
||||
h.repoMakeMember(ch, h.cUser)
|
||||
|
||||
flagCheck := func(ID uint64, flag string) {
|
||||
mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ID, MemberID: h.cUser.ID})
|
||||
mm, err := h.repoChMember().Find(types.ChannelMemberFilter{
|
||||
ChannelID: []uint64{ID},
|
||||
MemberID: []uint64{h.cUser.ID},
|
||||
})
|
||||
h.a.NoError(err)
|
||||
h.a.Len(mm, 1, "expecting 1 member")
|
||||
h.a.Equal(flag, string(mm[0].Flag), "expecting flags to match")
|
||||
|
||||
@@ -47,14 +47,13 @@ func (h helper) repoMakeMember(ch *types.Channel, u *sysTypes.User) *types.Chann
|
||||
}
|
||||
|
||||
func (h helper) repoChAssertNotMember(ch *types.Channel, u *sysTypes.User) {
|
||||
mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: h.cUser.ID})
|
||||
mm, err := h.repoChMember().Find(types.ChannelMemberFilter{ChannelID: []uint64{ch.ID}, MemberID: []uint64{h.cUser.ID}})
|
||||
h.a.NoError(err)
|
||||
h.a.NotNil(mm)
|
||||
h.a.NotContains(mm.AllMemberIDs(), u.ID, "not expecting to find a member")
|
||||
}
|
||||
|
||||
func (h helper) repoChAssertMember(ch *types.Channel, u *sysTypes.User, typ types.ChannelMembershipType) {
|
||||
mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: h.cUser.ID})
|
||||
mm, err := h.repoChMember().Find(types.ChannelMemberFilter{ChannelID: []uint64{ch.ID}, MemberID: []uint64{h.cUser.ID}})
|
||||
|
||||
h.a.NoError(err)
|
||||
h.a.NotNil(mm)
|
||||
|
||||
Reference in New Issue
Block a user