Files
OwnCord/Server/store/memstore.go
T
J3vbandClaude Fable 5 b329a61a7c fix(ws): route plugin broadcasts through the service-layer send check (W2-7)
requireChannelBroadcastAccess went through RequireChannelAccess, whose DM
branch checks only participant membership — a blocked user's plugin
broadcast could reach the person who blocked them — and it issued a raw
GetRoleByID per broadcast, bypassing the permission cache. The gate now
delegates to MessageService.CanPost (extracted over checkSendPermission),
so DM blocks, channel permissions, and future posting policy apply from
exactly one place; fails closed when no service is wired. First brick of
the permission-path unification. MemStore.GetDMRecipient gets an honest
implementation so the block path is testable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:03:03 +02:00

825 lines
22 KiB
Go

package store
import (
"context"
"database/sql"
"fmt"
"sync"
"time"
"github.com/owncord/server/db"
)
// compile-time interface check
var _ Store = (*MemStore)(nil)
// MemStore is a lightweight in-memory Store implementation for testing.
// Only the methods needed by service-layer tests are implemented;
// everything else panics with a descriptive message.
type MemStore struct {
mu sync.Mutex
// auto-increment counters
nextMsgID int64
nextChannelID int64
channels map[int64]*db.Channel
messages map[int64]*db.Message
users map[int64]*db.User
roles map[int64]*db.Role
// userID -> roleID
userRoles map[int64]int64
// roleID -> channelID -> override
channelOverrides map[int64]map[int64]db.ChannelOverride
// messageID -> userID -> emoji -> bool
reactions map[int64]map[int64]map[string]bool
// channelID -> set of participant userIDs
dmParticipants map[int64]map[int64]bool
// blockerID -> blockedID -> bool (bidirectional checked via IsEitherBlocked)
blocks map[int64]map[int64]bool
// userID -> channelID -> lastReadMessageID
readStates map[int64]map[int64]int64
// attachment id -> row. Tracks uploader_id/message_id so the atomic
// link-ownership guard is exercised against a store that really records
// ownership instead of a (nil, nil) stub.
attachments map[string]*db.Attachment
// Phase B Step 7 / Phase C Step 9 — events + plugin KV. Lazily initialised
// via ensureEvents() so existing tests that constructed a bare MemStore
// without these fields keep working.
eventsOnce sync.Once
eventStore *memEventStore
}
// NewMemStore creates an empty MemStore ready for use.
func NewMemStore() *MemStore {
return &MemStore{
channels: make(map[int64]*db.Channel),
messages: make(map[int64]*db.Message),
users: make(map[int64]*db.User),
roles: make(map[int64]*db.Role),
userRoles: make(map[int64]int64),
channelOverrides: make(map[int64]map[int64]db.ChannelOverride),
reactions: make(map[int64]map[int64]map[string]bool),
dmParticipants: make(map[int64]map[int64]bool),
attachments: make(map[string]*db.Attachment),
blocks: make(map[int64]map[int64]bool),
readStates: make(map[int64]map[int64]int64),
}
}
// ---------- helpers for test setup ----------
// SeedChannel inserts a channel directly (for test setup).
func (m *MemStore) SeedChannel(ch *db.Channel) {
m.mu.Lock()
defer m.mu.Unlock()
m.channels[ch.ID] = ch
if ch.ID >= m.nextChannelID {
m.nextChannelID = ch.ID + 1
}
}
// SeedUser inserts a user directly (for test setup).
func (m *MemStore) SeedUser(u *db.User) {
m.mu.Lock()
defer m.mu.Unlock()
m.users[u.ID] = u
}
// SeedRole inserts a role directly (for test setup).
func (m *MemStore) SeedRole(r *db.Role) {
m.mu.Lock()
defer m.mu.Unlock()
m.roles[r.ID] = r
}
// SeedUserRole assigns a role to a user (for test setup).
func (m *MemStore) SeedUserRole(userID, roleID int64) {
m.mu.Lock()
defer m.mu.Unlock()
m.userRoles[userID] = roleID
}
// SeedChannelOverride sets a channel permission override for a role (for test setup).
func (m *MemStore) SeedChannelOverride(roleID, channelID int64, allow, deny int64) {
m.mu.Lock()
defer m.mu.Unlock()
if m.channelOverrides[roleID] == nil {
m.channelOverrides[roleID] = make(map[int64]db.ChannelOverride)
}
m.channelOverrides[roleID][channelID] = db.ChannelOverride{Allow: allow, Deny: deny}
}
// SeedDMParticipant adds a user as a DM participant in a channel (for test setup).
func (m *MemStore) SeedDMParticipant(channelID, userID int64) {
m.mu.Lock()
defer m.mu.Unlock()
if m.dmParticipants[channelID] == nil {
m.dmParticipants[channelID] = make(map[int64]bool)
}
m.dmParticipants[channelID][userID] = true
}
// SeedBlock records a block relationship (for test setup).
func (m *MemStore) SeedBlock(blockerID, blockedID int64) {
m.mu.Lock()
defer m.mu.Unlock()
if m.blocks[blockerID] == nil {
m.blocks[blockerID] = make(map[int64]bool)
}
m.blocks[blockerID][blockedID] = true
}
// ---------- Store interface: top-level ----------
func (m *MemStore) Close() error { return nil }
func (m *MemStore) SQLDb() *sql.DB { return nil }
func (m *MemStore) WithTx(_ context.Context, fn func(Store) error) error { return fn(m) }
// ---------- MessageStore ----------
func (m *MemStore) CreateMessage(channelID, userID int64, content string, replyTo *int64) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.nextMsgID++
id := m.nextMsgID
m.messages[id] = &db.Message{
ID: id,
ChannelID: channelID,
UserID: userID,
Content: content,
ReplyTo: replyTo,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}
return id, nil
}
func (m *MemStore) GetMessage(id int64) (*db.Message, error) {
m.mu.Lock()
defer m.mu.Unlock()
msg, ok := m.messages[id]
if !ok {
return nil, fmt.Errorf("message %d not found", id)
}
// Return a copy to avoid aliasing.
cp := *msg
return &cp, nil
}
func (m *MemStore) GetMessages(_ int64, _ int64, _ int) ([]db.MessageWithUser, error) {
panic("memstore: not implemented: GetMessages")
}
func (m *MemStore) GetMessagesForAPI(_ int64, _ int64, _ int, _ int64) ([]db.MessageAPIResponse, error) {
return []db.MessageAPIResponse{}, nil
}
func (m *MemStore) EditMessage(id, userID int64, content string) error {
m.mu.Lock()
defer m.mu.Unlock()
msg, ok := m.messages[id]
if !ok {
return fmt.Errorf("message %d not found", id)
}
if msg.UserID != userID {
return fmt.Errorf("not message owner")
}
now := time.Now().UTC().Format(time.RFC3339)
msg.Content = content
msg.EditedAt = &now
return nil
}
func (m *MemStore) DeleteMessage(id, userID int64, isMod bool) error {
m.mu.Lock()
defer m.mu.Unlock()
msg, ok := m.messages[id]
if !ok {
return fmt.Errorf("message %d not found", id)
}
if !isMod && msg.UserID != userID {
return fmt.Errorf("not message owner")
}
msg.Deleted = true
return nil
}
func (m *MemStore) SearchMessages(_ string, _ *int64, _ int) ([]db.MessageSearchResult, error) {
return []db.MessageSearchResult{}, nil
}
func (m *MemStore) SearchMessagesInChannels(_ string, _ []int64, _ int) ([]db.MessageSearchResult, error) {
return []db.MessageSearchResult{}, nil
}
func (m *MemStore) GetPinnedMessages(_ int64, _ int64) ([]db.MessageAPIResponse, error) {
return []db.MessageAPIResponse{}, nil
}
func (m *MemStore) SetMessagePinned(_ int64, _ bool) error { return nil }
func (m *MemStore) AddReaction(messageID, userID int64, emoji string) error {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.messages[messageID]; !ok {
return fmt.Errorf("message %d not found", messageID)
}
if m.reactions[messageID] == nil {
m.reactions[messageID] = make(map[int64]map[string]bool)
}
if m.reactions[messageID][userID] == nil {
m.reactions[messageID][userID] = make(map[string]bool)
}
if m.reactions[messageID][userID][emoji] {
return fmt.Errorf("reaction already exists")
}
m.reactions[messageID][userID][emoji] = true
return nil
}
func (m *MemStore) RemoveReaction(messageID, userID int64, emoji string) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.reactions[messageID] == nil || m.reactions[messageID][userID] == nil || !m.reactions[messageID][userID][emoji] {
return fmt.Errorf("reaction not found")
}
delete(m.reactions[messageID][userID], emoji)
return nil
}
func (m *MemStore) GetReactions(_ int64) ([]db.ReactionCount, error) {
return []db.ReactionCount{}, nil
}
func (m *MemStore) UpdateReadState(userID, channelID, lastReadMessageID int64) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.readStates[userID] == nil {
m.readStates[userID] = make(map[int64]int64)
}
m.readStates[userID][channelID] = lastReadMessageID
return nil
}
func (m *MemStore) GetChannelUnreadCounts(_ int64) (map[int64]db.ChannelUnread, error) {
return map[int64]db.ChannelUnread{}, nil
}
func (m *MemStore) GetLatestMessageID(channelID int64) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
var latest int64
for _, msg := range m.messages {
if msg.ChannelID == channelID && msg.ID > latest {
latest = msg.ID
}
}
return latest, nil
}
// LinkAttachmentsToMessage mirrors the SQL guard in db.LinkAttachmentsToMessage:
// only unlinked attachments owned by uploaderID (or legacy rows with a nil
// uploader) are claimed; everything else is skipped, not an error.
func (m *MemStore) LinkAttachmentsToMessage(messageID, uploaderID int64, attachmentIDs []string) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
var n int64
for _, id := range attachmentIDs {
att, ok := m.attachments[id]
if !ok || att.MessageID != nil {
continue
}
if att.UploaderID != nil && *att.UploaderID != uploaderID {
continue
}
mid := messageID
att.MessageID = &mid
n++
}
return n, nil
}
func (m *MemStore) GetAttachmentsByMessageIDs(_ []int64) (map[int64][]db.AttachmentInfo, error) {
return map[int64][]db.AttachmentInfo{}, nil
}
// ---------- ChannelStore ----------
func (m *MemStore) ListChannels() ([]db.Channel, error) {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]db.Channel, 0, len(m.channels))
for _, ch := range m.channels {
out = append(out, *ch)
}
return out, nil
}
func (m *MemStore) GetChannel(id int64) (*db.Channel, error) {
m.mu.Lock()
defer m.mu.Unlock()
ch, ok := m.channels[id]
if !ok {
return nil, fmt.Errorf("channel %d not found", id)
}
cp := *ch
return &cp, nil
}
func (m *MemStore) CreateChannel(name, chanType, category, topic string, position int) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.nextChannelID++
id := m.nextChannelID
m.channels[id] = &db.Channel{
ID: id,
Name: name,
Type: chanType,
Category: category,
Topic: topic,
Position: position,
CreatedAt: time.Now().UTC().Format(time.RFC3339),
}
return id, nil
}
func (m *MemStore) UpdateChannel(_ int64, _, _ string, _ int) error {
panic("memstore: not implemented: UpdateChannel")
}
func (m *MemStore) DeleteChannel(_ int64) error {
panic("memstore: not implemented: DeleteChannel")
}
func (m *MemStore) SetChannelSlowMode(_ int64, _ int) error {
panic("memstore: not implemented: SetChannelSlowMode")
}
func (m *MemStore) SetChannelVoiceMaxUsers(_ int64, _ int) error {
panic("memstore: not implemented: SetChannelVoiceMaxUsers")
}
func (m *MemStore) GetChannelPermissions(channelID, roleID int64) (int64, int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
overrides, ok := m.channelOverrides[roleID]
if !ok {
return 0, 0, nil
}
o, ok := overrides[channelID]
if !ok {
return 0, 0, nil
}
return o.Allow, o.Deny, nil
}
func (m *MemStore) GetAllChannelPermissionsForRole(roleID int64) (map[int64]db.ChannelOverride, error) {
m.mu.Lock()
defer m.mu.Unlock()
overrides, ok := m.channelOverrides[roleID]
if !ok {
return map[int64]db.ChannelOverride{}, nil
}
// Copy map.
out := make(map[int64]db.ChannelOverride, len(overrides))
for k, v := range overrides {
out[k] = v
}
return out, nil
}
func (m *MemStore) GetChannelTypes(_ []int64) (map[int64]string, error) {
panic("memstore: not implemented: GetChannelTypes")
}
// ---------- UserStore ----------
func (m *MemStore) GetUserByID(id int64) (*db.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.users[id]
if !ok {
return nil, fmt.Errorf("user %d not found", id)
}
cp := *u
return &cp, nil
}
func (m *MemStore) GetUserByUsername(_ string) (*db.User, error) {
panic("memstore: not implemented: GetUserByUsername")
}
func (m *MemStore) CreateUser(_, _ string, _ int) (int64, error) {
panic("memstore: not implemented: CreateUser")
}
func (m *MemStore) CreateOwnerIfEmpty(_, _ string, _ int) (int64, error) {
panic("memstore: not implemented: CreateOwnerIfEmpty")
}
func (m *MemStore) CreateUserWithInvite(_, _ string, _ int, _ string) (int64, error) {
panic("memstore: not implemented: CreateUserWithInvite")
}
func (m *MemStore) UpdateUserProfile(_ int64, _ string, _ *string) error {
panic("memstore: not implemented: UpdateUserProfile")
}
func (m *MemStore) UpdateUserPassword(userID int64, hash string) error {
m.mu.Lock()
defer m.mu.Unlock()
if u, ok := m.users[userID]; ok {
u.PasswordHash = hash
}
return nil
}
func (m *MemStore) UpdateUserStatus(id int64, status string) error {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.users[id]
if !ok {
return fmt.Errorf("user %d not found", id)
}
u.Status = status
return nil
}
func (m *MemStore) UpdateUserTOTPSecret(_ int64, _ *string) error {
panic("memstore: not implemented: UpdateUserTOTPSecret")
}
func (m *MemStore) UpdateUserRole(_ int64, _ int64) error {
panic("memstore: not implemented: UpdateUserRole")
}
func (m *MemStore) ResetAllUserStatuses() error {
panic("memstore: not implemented: ResetAllUserStatuses")
}
func (m *MemStore) DeleteAccount(_ context.Context, _ int64) error {
panic("memstore: not implemented: DeleteAccount")
}
func (m *MemStore) ListMembers() ([]db.MemberSummary, error) {
panic("memstore: not implemented: ListMembers")
}
// ---------- SessionStore ----------
func (m *MemStore) CreateSession(_ int64, _, _, _ string) (int64, error) {
panic("memstore: not implemented: CreateSession")
}
func (m *MemStore) GetSessionByTokenHash(_ string) (*db.Session, error) {
panic("memstore: not implemented: GetSessionByTokenHash")
}
func (m *MemStore) GetSessionWithBanStatus(_ string) (*db.SessionWithBanStatus, error) {
panic("memstore: not implemented: GetSessionWithBanStatus")
}
func (m *MemStore) DeleteSession(_ string) error {
panic("memstore: not implemented: DeleteSession")
}
func (m *MemStore) DeleteOtherSessions(_ int64, _ int64) (int64, error) {
panic("memstore: not implemented: DeleteOtherSessions")
}
func (m *MemStore) DeleteExpiredSessions() error {
panic("memstore: not implemented: DeleteExpiredSessions")
}
func (m *MemStore) DeleteSessionByID(_ int64, _ int64) error {
panic("memstore: not implemented: DeleteSessionByID")
}
func (m *MemStore) TouchSession(_ string) error {
panic("memstore: not implemented: TouchSession")
}
func (m *MemStore) ListUserSessions(_ int64) ([]db.Session, error) {
panic("memstore: not implemented: ListUserSessions")
}
func (m *MemStore) ForceLogoutUser(_ int64) error {
panic("memstore: not implemented: ForceLogoutUser")
}
func (m *MemStore) GetUserSessions(_ int64) ([]db.Session, error) {
panic("memstore: not implemented: GetUserSessions")
}
// ---------- RoleStore ----------
func (m *MemStore) GetRoleByID(id int64) (*db.Role, error) {
m.mu.Lock()
defer m.mu.Unlock()
r, ok := m.roles[id]
if !ok {
return nil, fmt.Errorf("role %d not found", id)
}
cp := *r
return &cp, nil
}
func (m *MemStore) GetRoleForUser(userID int64) (*db.Role, error) {
m.mu.Lock()
defer m.mu.Unlock()
roleID, ok := m.userRoles[userID]
if !ok {
return nil, fmt.Errorf("no role for user %d", userID)
}
r, ok := m.roles[roleID]
if !ok {
return nil, fmt.Errorf("role %d not found", roleID)
}
cp := *r
return &cp, nil
}
func (m *MemStore) GetUserWithRole(_ int64) (*db.User, *db.Role, error) {
panic("memstore: not implemented: GetUserWithRole")
}
func (m *MemStore) ListRoles() ([]*db.Role, error) {
panic("memstore: not implemented: ListRoles")
}
// ---------- InviteStore ----------
func (m *MemStore) CreateInvite(_ int64, _ int, _ *time.Time) (string, error) {
panic("memstore: not implemented: CreateInvite")
}
func (m *MemStore) GetInvite(_ string) (*db.Invite, error) {
panic("memstore: not implemented: GetInvite")
}
func (m *MemStore) ListInvites() ([]*db.Invite, error) {
panic("memstore: not implemented: ListInvites")
}
func (m *MemStore) UseInviteAtomic(_ string) error {
panic("memstore: not implemented: UseInviteAtomic")
}
func (m *MemStore) RevokeInvite(_ string) error {
panic("memstore: not implemented: RevokeInvite")
}
// ---------- VoiceStore ----------
func (m *MemStore) JoinVoiceChannel(_ int64, _ int64) error {
panic("memstore: not implemented: JoinVoiceChannel")
}
func (m *MemStore) JoinVoiceChannelIfCapacity(_ int64, _ int64, _ int) error {
panic("memstore: not implemented: JoinVoiceChannelIfCapacity")
}
func (m *MemStore) LeaveVoiceChannel(_ int64) error {
panic("memstore: not implemented: LeaveVoiceChannel")
}
func (m *MemStore) LeaveVoiceChannelIfMatch(_ int64, _ int64, _ string) (bool, error) {
panic("memstore: not implemented: LeaveVoiceChannelIfMatch")
}
func (m *MemStore) GetVoiceState(_ int64) (*db.VoiceState, error) {
panic("memstore: not implemented: GetVoiceState")
}
func (m *MemStore) GetChannelVoiceStates(_ int64) ([]db.VoiceState, error) {
panic("memstore: not implemented: GetChannelVoiceStates")
}
func (m *MemStore) GetAllVoiceStates() ([]db.VoiceState, error) {
panic("memstore: not implemented: GetAllVoiceStates")
}
func (m *MemStore) UpdateVoiceMute(_ int64, _ bool) error {
panic("memstore: not implemented: UpdateVoiceMute")
}
func (m *MemStore) UpdateVoiceDeafen(_ int64, _ bool) error {
panic("memstore: not implemented: UpdateVoiceDeafen")
}
func (m *MemStore) ClearVoiceState(_ int64) error {
panic("memstore: not implemented: ClearVoiceState")
}
func (m *MemStore) ClearAllVoiceStates() error {
panic("memstore: not implemented: ClearAllVoiceStates")
}
func (m *MemStore) CountActiveCameras(_ int64) (int, error) {
panic("memstore: not implemented: CountActiveCameras")
}
func (m *MemStore) UpdateVoiceCamera(_ int64, _ bool) error {
panic("memstore: not implemented: UpdateVoiceCamera")
}
func (m *MemStore) EnableCameraIfUnderLimit(_ int64, _ int64, _ int) (bool, error) {
panic("memstore: not implemented: EnableCameraIfUnderLimit")
}
func (m *MemStore) UpdateVoiceScreenshare(_ int64, _ bool) error {
panic("memstore: not implemented: UpdateVoiceScreenshare")
}
func (m *MemStore) CountChannelVoiceUsers(_ int64) (int, error) {
panic("memstore: not implemented: CountChannelVoiceUsers")
}
// ---------- DMStore ----------
func (m *MemStore) GetOrCreateDMChannel(_ int64, _ int64) (*db.Channel, bool, error) {
panic("memstore: not implemented: GetOrCreateDMChannel")
}
func (m *MemStore) GetUserDMChannels(_ int64) ([]db.DMChannelInfo, error) {
return []db.DMChannelInfo{}, nil
}
func (m *MemStore) OpenDM(_, _ int64) error { return nil }
func (m *MemStore) CloseDM(_, _ int64) error { return nil }
func (m *MemStore) IsDMParticipant(userID, channelID int64) (bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
participants, ok := m.dmParticipants[channelID]
if !ok {
return false, nil
}
return participants[userID], nil
}
func (m *MemStore) GetDMParticipantIDs(channelID int64) ([]int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
participants, ok := m.dmParticipants[channelID]
if !ok {
return []int64{}, nil
}
ids := make([]int64, 0, len(participants))
for id := range participants {
ids = append(ids, id)
}
return ids, nil
}
func (m *MemStore) GetDMRecipient(channelID, userID int64) (*db.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
for uid := range m.dmParticipants[channelID] {
if uid != userID {
return m.users[uid], nil
}
}
return nil, nil
}
// ---------- BlockStore ----------
func (m *MemStore) BlockUser(_, _ int64) error { return nil }
func (m *MemStore) UnblockUser(_, _ int64) error { return nil }
func (m *MemStore) IsBlocked(blockerID, blockedID int64) (bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.blocks[blockerID] != nil && m.blocks[blockerID][blockedID] {
return true, nil
}
return false, nil
}
func (m *MemStore) IsEitherBlocked(userA, userB int64) (bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.blocks[userA] != nil && m.blocks[userA][userB] {
return true, nil
}
if m.blocks[userB] != nil && m.blocks[userB][userA] {
return true, nil
}
return false, nil
}
func (m *MemStore) ListBlockedUsers(_ int64) ([]int64, error) {
return []int64{}, nil
}
// ---------- AttachmentStore ----------
func (m *MemStore) CreateAttachment(id string, uploaderID int64, filename, storedAs, mimeType string, size int64, _, _ *int) error {
m.mu.Lock()
defer m.mu.Unlock()
uid := uploaderID
m.attachments[id] = &db.Attachment{
ID: id, UploaderID: &uid, Filename: filename,
StoredAs: storedAs, MimeType: mimeType, Size: size,
}
return nil
}
func (m *MemStore) GetAttachmentByID(id string) (*db.Attachment, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.attachments[id], nil
}
func (m *MemStore) GetAttachmentWithChannel(_ string) (*db.AttachmentAccess, error) {
panic("memstore: not implemented: GetAttachmentWithChannel")
}
func (m *MemStore) DeleteOrphanedAttachments(_ string) ([]string, error) {
panic("memstore: not implemented: DeleteOrphanedAttachments")
}
// ---------- AdminStore ----------
func (m *MemStore) UserCount() (int64, error) {
panic("memstore: not implemented: UserCount")
}
func (m *MemStore) GetServerStats() (*db.ServerStats, error) {
panic("memstore: not implemented: GetServerStats")
}
func (m *MemStore) ListAllUsers(_ int, _ int) ([]db.UserWithRole, error) {
panic("memstore: not implemented: ListAllUsers")
}
func (m *MemStore) BanUser(userID int64, reason string, _ *time.Time) error {
m.mu.Lock()
defer m.mu.Unlock()
if u, ok := m.users[userID]; ok {
u.Banned = true
r := reason
u.BanReason = &r
}
return nil
}
func (m *MemStore) UnbanUser(userID int64) error {
m.mu.Lock()
defer m.mu.Unlock()
if u, ok := m.users[userID]; ok {
u.Banned = false
u.BanReason = nil
u.BanExpires = nil
}
return nil
}
func (m *MemStore) LogAudit(_ int64, _, _ string, _ int64, _ string) error {
return nil
}
func (m *MemStore) GetAuditLog(_ int, _ int) ([]db.AuditEntry, error) {
panic("memstore: not implemented: GetAuditLog")
}
func (m *MemStore) AdminCreateChannel(_, _, _, _ string, _ int) (int64, error) {
panic("memstore: not implemented: AdminCreateChannel")
}
func (m *MemStore) AdminUpdateChannel(_ int64, _, _ string, _, _ int, _ bool) error {
panic("memstore: not implemented: AdminUpdateChannel")
}
func (m *MemStore) AdminDeleteChannel(_ int64) error {
panic("memstore: not implemented: AdminDeleteChannel")
}
func (m *MemStore) BackupTo(_ string) error {
panic("memstore: not implemented: BackupTo")
}
func (m *MemStore) BackupToSafe(_, _ string) error {
panic("memstore: not implemented: BackupToSafe")
}
func (m *MemStore) CountUsersWithoutTOTP() (int, error) {
panic("memstore: not implemented: CountUsersWithoutTOTP")
}
// ---------- SettingsStore ----------
func (m *MemStore) GetSetting(_ string) (string, error) {
panic("memstore: not implemented: GetSetting")
}
func (m *MemStore) SetSetting(_, _ string) error {
panic("memstore: not implemented: SetSetting")
}
func (m *MemStore) GetAllSettings() (map[string]string, error) {
panic("memstore: not implemented: GetAllSettings")
}