508 lines
19 KiB
Go
508 lines
19 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/jackc/pgx/v5"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store/postgres/sqlcgen"
|
|
)
|
|
|
|
func (s *ChannelStore) getChannelForMember(ctx context.Context, db sqlcgen.DBTX, viewerUserID, channelID int64) (domain.Channel, domain.ChannelMember, error) {
|
|
member, err := s.getChannelMember(ctx, db, channelID, viewerUserID)
|
|
if err != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, err
|
|
}
|
|
if err := validateChannelMemberVisible(member); err != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, err
|
|
}
|
|
ch, err := s.channelByID(ctx, db, channelID)
|
|
if err != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, err
|
|
}
|
|
return ch, member, nil
|
|
}
|
|
|
|
func (s *ChannelStore) getChannelForMemberOrLinkedGuest(ctx context.Context, db sqlcgen.DBTX, viewerUserID, channelID int64) (domain.Channel, domain.ChannelMember, error) {
|
|
channel, member, err := s.getChannelForMember(ctx, db, viewerUserID, channelID)
|
|
if !errors.Is(err, domain.ErrChannelPrivate) {
|
|
return channel, member, err
|
|
}
|
|
target, targetErr := s.channelByID(ctx, db, channelID)
|
|
if targetErr != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, targetErr
|
|
}
|
|
guest, allowed, guestErr := s.getLinkedDiscussionGuest(ctx, db, viewerUserID, target)
|
|
if guestErr != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, guestErr
|
|
}
|
|
if !allowed {
|
|
return domain.Channel{}, domain.ChannelMember{}, err
|
|
}
|
|
return target, guest, nil
|
|
}
|
|
|
|
// getLinkedDiscussionGuest authorizes a private discussion group through an
|
|
// active membership in its bidirectionally linked broadcast. The returned
|
|
// member is computed only and must never be persisted. An explicit target ban
|
|
// or kick takes precedence over the source-channel membership.
|
|
func (s *ChannelStore) getLinkedDiscussionGuest(ctx context.Context, db sqlcgen.DBTX, viewerUserID int64, target domain.Channel) (domain.ChannelMember, bool, error) {
|
|
if target.Broadcast || !target.Megagroup || target.LinkedChatID == 0 {
|
|
return domain.ChannelMember{}, false, nil
|
|
}
|
|
existing, err := s.getChannelMember(ctx, db, target.ID, viewerUserID)
|
|
if err == nil {
|
|
if existing.Status == domain.ChannelMemberBanned || existing.Status == domain.ChannelMemberKicked || existing.BannedRights.ViewMessages {
|
|
return domain.ChannelMember{}, false, domain.ErrChannelUserBanned
|
|
}
|
|
if existing.Status == domain.ChannelMemberActive {
|
|
return existing, false, nil
|
|
}
|
|
} else if !errors.Is(err, domain.ErrChannelPrivate) {
|
|
return domain.ChannelMember{}, false, err
|
|
}
|
|
source, err := s.channelByID(ctx, db, target.LinkedChatID)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrChannelInvalid) {
|
|
return domain.ChannelMember{}, false, nil
|
|
}
|
|
return domain.ChannelMember{}, false, err
|
|
}
|
|
if !source.Broadcast || source.LinkedChatID != target.ID {
|
|
return domain.ChannelMember{}, false, nil
|
|
}
|
|
sourceMember, err := s.getChannelMember(ctx, db, source.ID, viewerUserID)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrChannelPrivate) {
|
|
return domain.ChannelMember{}, false, nil
|
|
}
|
|
return domain.ChannelMember{}, false, err
|
|
}
|
|
if err := validateChannelMemberVisible(sourceMember); err != nil {
|
|
return domain.ChannelMember{}, false, err
|
|
}
|
|
guest := domain.ChannelMember{
|
|
ChannelID: target.ID,
|
|
UserID: viewerUserID,
|
|
Status: domain.ChannelMemberLeft,
|
|
Role: domain.ChannelRoleMember,
|
|
Guest: true,
|
|
}
|
|
if s.memberCacheActive(db) {
|
|
s.memberCache.put(guest)
|
|
}
|
|
return guest, true, nil
|
|
}
|
|
|
|
// listLinkedDiscussionGuests is the bounded batch equivalent of
|
|
// getLinkedDiscussionGuest. GetChannels uses it after loading the requested
|
|
// channel rows so messages.getPeerDialogs can materialize linked discussion
|
|
// histories without an N+1 query per requested peer.
|
|
//
|
|
// The target membership predicate deliberately excludes active, kicked,
|
|
// banned and view-messages-banned rows. Active members were projected by the
|
|
// primary GetChannels query; explicit target denial must always win over the
|
|
// source broadcast membership. Returned members are transient and are never
|
|
// persisted to channel_members/channel_dialogs.
|
|
func (s *ChannelStore) listLinkedDiscussionGuests(ctx context.Context, db sqlcgen.DBTX, viewerUserID int64, targetIDs []int64) (map[int64]domain.ChannelMember, error) {
|
|
out := make(map[int64]domain.ChannelMember)
|
|
if viewerUserID == 0 || len(targetIDs) == 0 {
|
|
return out, nil
|
|
}
|
|
rows, err := db.Query(ctx, `
|
|
SELECT target.id
|
|
FROM channels target
|
|
JOIN channels source
|
|
ON source.id = target.linked_chat_id
|
|
AND NOT source.deleted
|
|
AND source.broadcast
|
|
AND source.linked_chat_id = target.id
|
|
JOIN channel_members source_member
|
|
ON source_member.channel_id = source.id
|
|
AND source_member.user_id = $1
|
|
AND source_member.status = 'active'
|
|
AND NOT COALESCE((source_member.banned_rights->>'ViewMessages')::boolean, false)
|
|
LEFT JOIN channel_members target_member
|
|
ON target_member.channel_id = target.id
|
|
AND target_member.user_id = $1
|
|
WHERE target.id = ANY($2::bigint[])
|
|
AND NOT target.deleted
|
|
AND target.megagroup
|
|
AND NOT target.broadcast
|
|
AND (
|
|
target_member.user_id IS NULL
|
|
OR (
|
|
target_member.status NOT IN ('active', 'banned', 'kicked')
|
|
AND NOT COALESCE((target_member.banned_rights->>'ViewMessages')::boolean, false)
|
|
)
|
|
)
|
|
ORDER BY target.id`, viewerUserID, targetIDs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list linked discussion guests: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var channelID int64
|
|
if err := rows.Scan(&channelID); err != nil {
|
|
return nil, fmt.Errorf("scan linked discussion guest: %w", err)
|
|
}
|
|
out[channelID] = domain.ChannelMember{
|
|
ChannelID: channelID,
|
|
UserID: viewerUserID,
|
|
Status: domain.ChannelMemberLeft,
|
|
Role: domain.ChannelRoleMember,
|
|
Guest: true,
|
|
}
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate linked discussion guests: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *ChannelStore) getPublicPreviewMember(ctx context.Context, db sqlcgen.DBTX, viewerUserID int64, ch domain.Channel) (domain.ChannelMember, error) {
|
|
member, err := s.getChannelMember(ctx, db, ch.ID, viewerUserID)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrChannelPrivate) {
|
|
return publicPreviewMember(ch, viewerUserID, domain.ChannelMember{}, false), nil
|
|
}
|
|
return domain.ChannelMember{}, err
|
|
}
|
|
if member.Status == domain.ChannelMemberBanned || member.Status == domain.ChannelMemberKicked || member.BannedRights.ViewMessages {
|
|
return domain.ChannelMember{}, domain.ErrChannelUserBanned
|
|
}
|
|
return publicPreviewMember(ch, viewerUserID, member, true), nil
|
|
}
|
|
|
|
func (s *ChannelStore) getChannelMember(ctx context.Context, db sqlcgen.DBTX, channelID, userID int64) (domain.ChannelMember, error) {
|
|
if s.memberCacheActive(db) {
|
|
return s.memberCache.getOrLoad(ctx, channelID, userID, func() (domain.ChannelMember, error) {
|
|
return getChannelMemberByID(ctx, db, channelID, userID)
|
|
})
|
|
}
|
|
return getChannelMemberByID(ctx, db, channelID, userID)
|
|
}
|
|
|
|
func getChannelMemberByID(ctx context.Context, db sqlcgen.DBTX, channelID, userID int64) (domain.ChannelMember, error) {
|
|
row := db.QueryRow(ctx, `
|
|
SELECT channel_id, user_id, inviter_user_id, role, status, joined_at, left_at,
|
|
admin_rights::text, banned_rights::text, rank, available_min_id, available_min_pts,
|
|
read_inbox_max_id, read_outbox_max_id, unread_mark, slowmode_last_send_date
|
|
FROM channel_members
|
|
WHERE channel_id = $1 AND user_id = $2`, channelID, userID)
|
|
member, err := scanChannelMember(row)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return domain.ChannelMember{}, domain.ErrChannelPrivate
|
|
}
|
|
if err != nil {
|
|
return domain.ChannelMember{}, err
|
|
}
|
|
return member, nil
|
|
}
|
|
|
|
func upsertChannelMemberTx(ctx context.Context, tx pgx.Tx, channel domain.Channel, member domain.ChannelMember) error {
|
|
adminRights, err := marshalJSON(member.AdminRights, "{}")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bannedRights, err := marshalJSON(member.BannedRights, "{}")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
INSERT INTO channel_members (
|
|
channel_id, user_id, inviter_user_id, role, status, joined_at, left_at, admin_rights, banned_rights,
|
|
rank, available_min_id, available_min_pts, read_inbox_max_id, read_outbox_max_id, unread_mark, slowmode_last_send_date
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
|
ON CONFLICT (channel_id, user_id) DO UPDATE SET
|
|
inviter_user_id = EXCLUDED.inviter_user_id,
|
|
role = EXCLUDED.role,
|
|
status = EXCLUDED.status,
|
|
joined_at = EXCLUDED.joined_at,
|
|
left_at = EXCLUDED.left_at,
|
|
admin_rights = EXCLUDED.admin_rights,
|
|
banned_rights = EXCLUDED.banned_rights,
|
|
rank = EXCLUDED.rank,
|
|
available_min_id = GREATEST(channel_members.available_min_id, EXCLUDED.available_min_id),
|
|
available_min_pts = GREATEST(channel_members.available_min_pts, EXCLUDED.available_min_pts),
|
|
read_inbox_max_id = GREATEST(channel_members.read_inbox_max_id, EXCLUDED.read_inbox_max_id),
|
|
updated_at = now()`,
|
|
member.ChannelID, member.UserID, member.InviterUserID, string(member.Role), string(member.Status),
|
|
member.JoinedAt, member.LeftAt, adminRights, bannedRights, member.Rank, member.AvailableMinID,
|
|
member.AvailableMinPts, member.ReadInboxMaxID, member.ReadOutboxMaxID, member.UnreadMark, member.SlowmodeLastSendDate); err != nil {
|
|
return fmt.Errorf("upsert channel member: %w", err)
|
|
}
|
|
return upsertUserChannelMemberIndexTx(ctx, tx, channel, member)
|
|
}
|
|
|
|
func upsertUserChannelMemberIndexTx(ctx context.Context, tx pgx.Tx, channel domain.Channel, member domain.ChannelMember) error {
|
|
if channel.ID == 0 || member.UserID == 0 {
|
|
return nil
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
INSERT INTO user_channel_member_index (
|
|
user_id, channel_id, status, megagroup, broadcast, deleted,
|
|
role, left_at, forum, public_username, can_pin_messages
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
|
ON CONFLICT (user_id, channel_id) DO UPDATE SET
|
|
status = EXCLUDED.status,
|
|
megagroup = EXCLUDED.megagroup,
|
|
broadcast = EXCLUDED.broadcast,
|
|
deleted = EXCLUDED.deleted,
|
|
role = EXCLUDED.role,
|
|
left_at = EXCLUDED.left_at,
|
|
forum = EXCLUDED.forum,
|
|
public_username = EXCLUDED.public_username,
|
|
can_pin_messages = EXCLUDED.can_pin_messages,
|
|
updated_at = now()`,
|
|
member.UserID, channel.ID, string(member.Status), channel.Megagroup, channel.Broadcast, channel.Deleted,
|
|
string(member.Role), member.LeftAt, channel.Forum, channel.Username != "", channelMemberCanPinMessages(member)); err != nil {
|
|
return fmt.Errorf("upsert user channel member index: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func channelMemberCanPinMessages(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || (member.Role == domain.ChannelRoleAdmin && member.AdminRights.PinMessages)
|
|
}
|
|
|
|
func markUserChannelMemberIndexDeletedTx(ctx context.Context, tx pgx.Tx, channelID int64, deleted bool) error {
|
|
if channelID == 0 {
|
|
return nil
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE user_channel_member_index
|
|
SET deleted = $2, updated_at = now()
|
|
WHERE channel_id = $1`, channelID, deleted); err != nil {
|
|
return fmt.Errorf("mark user channel member index deleted: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func markUserChannelMemberIndexPublicTx(ctx context.Context, tx pgx.Tx, channelID int64, public bool) error {
|
|
if channelID == 0 {
|
|
return nil
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE user_channel_member_index
|
|
SET public_username = $2, updated_at = now()
|
|
WHERE channel_id = $1`, channelID, public); err != nil {
|
|
return fmt.Errorf("mark user channel member index public: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func markUserChannelMemberIndexForumTx(ctx context.Context, tx pgx.Tx, channelID int64, forum bool) error {
|
|
if channelID == 0 {
|
|
return nil
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
UPDATE user_channel_member_index
|
|
SET forum = $2, updated_at = now()
|
|
WHERE channel_id = $1`, channelID, forum); err != nil {
|
|
return fmt.Errorf("mark user channel member index forum: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func scanChannelWithMember(row rowScanner) (domain.Channel, domain.ChannelMember, error) {
|
|
var ch domain.Channel
|
|
var member domain.ChannelMember
|
|
var defaultRights, reactionPolicy, adminRights, bannedRights string
|
|
var wallpaper *string
|
|
var role, status string
|
|
dest := append(channelScanDest(&ch, &defaultRights, &reactionPolicy, &wallpaper),
|
|
&member.ChannelID, &member.UserID, &member.InviterUserID, &role, &status,
|
|
&member.JoinedAt, &member.LeftAt, &adminRights, &bannedRights, &member.Rank,
|
|
&member.AvailableMinID, &member.AvailableMinPts, &member.ReadInboxMaxID, &member.ReadOutboxMaxID, &member.UnreadMark, &member.SlowmodeLastSendDate,
|
|
)
|
|
if err := row.Scan(dest...); err != nil {
|
|
return domain.Channel{}, domain.ChannelMember{}, err
|
|
}
|
|
member.Role = domain.ChannelMemberRole(role)
|
|
member.Status = domain.ChannelMemberStatus(status)
|
|
finishChannelScan(&ch, defaultRights, reactionPolicy, wallpaper)
|
|
_ = json.Unmarshal([]byte(adminRights), &member.AdminRights)
|
|
_ = json.Unmarshal([]byte(bannedRights), &member.BannedRights)
|
|
return ch, member, nil
|
|
}
|
|
|
|
func scanChannelWithViewerMember(row rowScanner) (domain.Channel, bool, error) {
|
|
var ch domain.Channel
|
|
var viewerMember bool
|
|
var rights, reactionPolicy string
|
|
var wallpaper *string
|
|
dest := append(channelScanDest(&ch, &rights, &reactionPolicy, &wallpaper),
|
|
&viewerMember,
|
|
)
|
|
if err := row.Scan(dest...); err != nil {
|
|
return domain.Channel{}, false, err
|
|
}
|
|
finishChannelScan(&ch, rights, reactionPolicy, wallpaper)
|
|
return ch, viewerMember, nil
|
|
}
|
|
|
|
func scanChannelMember(row rowScanner) (domain.ChannelMember, error) {
|
|
var member domain.ChannelMember
|
|
var adminRights, bannedRights string
|
|
var role, status string
|
|
if err := row.Scan(
|
|
&member.ChannelID, &member.UserID, &member.InviterUserID, &role, &status,
|
|
&member.JoinedAt, &member.LeftAt, &adminRights, &bannedRights, &member.Rank,
|
|
&member.AvailableMinID, &member.AvailableMinPts, &member.ReadInboxMaxID, &member.ReadOutboxMaxID, &member.UnreadMark, &member.SlowmodeLastSendDate,
|
|
); err != nil {
|
|
return domain.ChannelMember{}, err
|
|
}
|
|
member.Role = domain.ChannelMemberRole(role)
|
|
member.Status = domain.ChannelMemberStatus(status)
|
|
_ = json.Unmarshal([]byte(adminRights), &member.AdminRights)
|
|
_ = json.Unmarshal([]byte(bannedRights), &member.BannedRights)
|
|
return member, nil
|
|
}
|
|
|
|
func scanChannelMemberWithCount(row rowScanner) (domain.ChannelMember, int, error) {
|
|
var member domain.ChannelMember
|
|
var adminRights, bannedRights string
|
|
var role, status string
|
|
var count int
|
|
if err := row.Scan(
|
|
&member.ChannelID, &member.UserID, &member.InviterUserID, &role, &status,
|
|
&member.JoinedAt, &member.LeftAt, &adminRights, &bannedRights, &member.Rank,
|
|
&member.AvailableMinID, &member.AvailableMinPts, &member.ReadInboxMaxID, &member.ReadOutboxMaxID, &member.UnreadMark, &member.SlowmodeLastSendDate,
|
|
&count,
|
|
); err != nil {
|
|
return domain.ChannelMember{}, 0, err
|
|
}
|
|
member.Role = domain.ChannelMemberRole(role)
|
|
member.Status = domain.ChannelMemberStatus(status)
|
|
_ = json.Unmarshal([]byte(adminRights), &member.AdminRights)
|
|
_ = json.Unmarshal([]byte(bannedRights), &member.BannedRights)
|
|
return member, count, nil
|
|
}
|
|
|
|
func validateChannelMemberVisible(member domain.ChannelMember) error {
|
|
switch member.Status {
|
|
case domain.ChannelMemberActive:
|
|
if member.BannedRights.ViewMessages {
|
|
return domain.ErrChannelUserBanned
|
|
}
|
|
return nil
|
|
case domain.ChannelMemberBanned, domain.ChannelMemberKicked:
|
|
return domain.ErrChannelUserBanned
|
|
default:
|
|
return domain.ErrChannelPrivate
|
|
}
|
|
}
|
|
|
|
func canPostChannel(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || (member.Role == domain.ChannelRoleAdmin && member.AdminRights.PostMessages)
|
|
}
|
|
|
|
func isChannelAdmin(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || member.Role == domain.ChannelRoleAdmin
|
|
}
|
|
|
|
func canManageDiscussionBroadcast(member domain.ChannelMember) bool {
|
|
return canChangeChannelInfo(member)
|
|
}
|
|
|
|
func canManageDiscussionGroup(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || (member.Role == domain.ChannelRoleAdmin && member.AdminRights.PinMessages)
|
|
}
|
|
|
|
func canAddChannelAdmins(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || (member.Role == domain.ChannelRoleAdmin && member.AdminRights.AddAdmins)
|
|
}
|
|
|
|
func canBanChannelUsers(member domain.ChannelMember) bool {
|
|
return member.Role == domain.ChannelRoleCreator || (member.Role == domain.ChannelRoleAdmin && member.AdminRights.BanUsers)
|
|
}
|
|
|
|
func publicPreviewMember(channel domain.Channel, userID int64, existing domain.ChannelMember, found bool) domain.ChannelMember {
|
|
member := domain.ChannelMember{
|
|
ChannelID: channel.ID,
|
|
UserID: userID,
|
|
Role: domain.ChannelRoleMember,
|
|
Status: domain.ChannelMemberLeft,
|
|
AvailableMinID: channelInitialAvailableMinID(channel),
|
|
AvailableMinPts: channelInitialAvailableMinPts(channel),
|
|
ReadInboxMaxID: channel.TopMessageID,
|
|
ReadOutboxMaxID: channel.TopMessageID,
|
|
}
|
|
if found {
|
|
member.InviterUserID = existing.InviterUserID
|
|
member.JoinedAt = existing.JoinedAt
|
|
member.LeftAt = existing.LeftAt
|
|
member.AvailableMinID = maxInt(member.AvailableMinID, existing.AvailableMinID)
|
|
member.AvailableMinPts = maxInt(member.AvailableMinPts, existing.AvailableMinPts)
|
|
member.ReadInboxMaxID = maxInt(member.ReadInboxMaxID, existing.ReadInboxMaxID)
|
|
member.ReadOutboxMaxID = maxInt(member.ReadOutboxMaxID, existing.ReadOutboxMaxID)
|
|
}
|
|
return member
|
|
}
|
|
|
|
func (s *ChannelStore) monoforumAdminPreview(ctx context.Context, db sqlcgen.DBTX, viewerUserID int64, mono domain.Channel) (domain.ChannelMember, domain.Channel, bool, error) {
|
|
if viewerUserID == 0 || !mono.Monoforum || mono.LinkedMonoforumID == 0 {
|
|
return domain.ChannelMember{}, domain.Channel{}, false, nil
|
|
}
|
|
parent, parentMember, err := s.getChannelForMember(ctx, db, viewerUserID, mono.LinkedMonoforumID)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrChannelPrivate) {
|
|
return domain.ChannelMember{}, domain.Channel{}, false, nil
|
|
}
|
|
return domain.ChannelMember{}, domain.Channel{}, false, err
|
|
}
|
|
if !isChannelAdmin(parentMember) {
|
|
return domain.ChannelMember{}, domain.Channel{}, false, nil
|
|
}
|
|
return syntheticMonoforumAdminMember(mono, parentMember), parent, true, nil
|
|
}
|
|
|
|
func syntheticMonoforumAdminMember(mono domain.Channel, parentMember domain.ChannelMember) domain.ChannelMember {
|
|
member := parentMember
|
|
member.ChannelID = mono.ID
|
|
member.Status = domain.ChannelMemberActive
|
|
if mono.CreatorUserID == parentMember.UserID {
|
|
member.Role = domain.ChannelRoleCreator
|
|
} else {
|
|
member.Role = domain.ChannelRoleAdmin
|
|
}
|
|
member.AvailableMinID = 0
|
|
member.AvailableMinPts = 0
|
|
member.ReadInboxMaxID = mono.TopMessageID
|
|
member.ReadOutboxMaxID = mono.TopMessageID
|
|
member.UnreadMark = false
|
|
member.SlowmodeLastSendDate = 0
|
|
return member
|
|
}
|
|
|
|
func zeroChannelAdminRights(rights domain.ChannelAdminRights) bool {
|
|
return rights == domain.ChannelAdminRights{}
|
|
}
|
|
|
|
func zeroChannelBannedRights(rights domain.ChannelBannedRights) bool {
|
|
return rights == domain.ChannelBannedRights{}
|
|
}
|
|
|
|
func creatorChannelMember(channelID, userID int64, date int) domain.ChannelMember {
|
|
return domain.ChannelMember{
|
|
ChannelID: channelID,
|
|
UserID: userID,
|
|
Role: domain.ChannelRoleCreator,
|
|
Status: domain.ChannelMemberActive,
|
|
JoinedAt: date,
|
|
AdminRights: domain.CreatorChannelAdminRights(),
|
|
}
|
|
}
|
|
|
|
func channelMemberIDs(members []domain.ChannelMember) []int64 {
|
|
out := make([]int64, 0, len(members))
|
|
for _, member := range members {
|
|
if member.UserID != 0 {
|
|
out = append(out, member.UserID)
|
|
}
|
|
}
|
|
return out
|
|
}
|