mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-02 19:43:10 +03:00
add ModerationService, VoiceService, MemStore, permission tests
- ModerationService: ban/unban with validation and audit logging - VoiceService: join (with capacity check), leave, mute, deafen, camera (with video limit), screenshare (with permission check) - MemStore: in-memory Store implementation for service unit tests - Permission tests: cache hit/miss, invalidation, TTL behavior - Add Moderation and Voice to Services struct https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/owncord/server/store"
|
||||
)
|
||||
|
||||
// ModerationService handles user ban/unban operations.
|
||||
type ModerationService struct {
|
||||
st store.Store
|
||||
}
|
||||
|
||||
// NewModerationService creates a ModerationService.
|
||||
func NewModerationService(st store.Store) *ModerationService {
|
||||
return &ModerationService{st: st}
|
||||
}
|
||||
|
||||
// BanUser bans a target user. Validates the target exists and
|
||||
// prevents self-banning.
|
||||
func (s *ModerationService) BanUser(actorID, targetID int64, reason string, expires *time.Time) error {
|
||||
if targetID <= 0 {
|
||||
return fmt.Errorf("%w: user_id must be positive", ErrBadRequest)
|
||||
}
|
||||
if actorID == targetID {
|
||||
return fmt.Errorf("%w: cannot ban yourself", ErrBadRequest)
|
||||
}
|
||||
|
||||
target, err := s.st.GetUserByID(targetID)
|
||||
if err != nil || target == nil {
|
||||
return fmt.Errorf("%w: user not found", ErrNotFound)
|
||||
}
|
||||
|
||||
if err := s.st.BanUser(targetID, reason, expires); err != nil {
|
||||
return fmt.Errorf("%w: failed to ban user", ErrInternal)
|
||||
}
|
||||
|
||||
if err := s.st.LogAudit(actorID, "ban", "user", targetID, reason); err != nil {
|
||||
slog.Error("failed to log audit entry", "error", err)
|
||||
}
|
||||
|
||||
slog.Info("user banned", "actor_id", actorID, "target_id", targetID, "reason", reason)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnbanUser removes a ban on a target user.
|
||||
func (s *ModerationService) UnbanUser(actorID, targetID int64) error {
|
||||
if targetID <= 0 {
|
||||
return fmt.Errorf("%w: user_id must be positive", ErrBadRequest)
|
||||
}
|
||||
|
||||
target, err := s.st.GetUserByID(targetID)
|
||||
if err != nil || target == nil {
|
||||
return fmt.Errorf("%w: user not found", ErrNotFound)
|
||||
}
|
||||
|
||||
if err := s.st.UnbanUser(targetID); err != nil {
|
||||
return fmt.Errorf("%w: failed to unban user", ErrInternal)
|
||||
}
|
||||
|
||||
if err := s.st.LogAudit(actorID, "unban", "user", targetID, ""); err != nil {
|
||||
slog.Error("failed to log audit entry", "error", err)
|
||||
}
|
||||
|
||||
slog.Info("user unbanned", "actor_id", actorID, "target_id", targetID)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/owncord/server/db"
|
||||
"github.com/owncord/server/permissions"
|
||||
"github.com/owncord/server/store"
|
||||
)
|
||||
|
||||
// newTestPermService creates a PermissionService backed by a MemStore
|
||||
// pre-populated with a single role and user.
|
||||
func newTestPermService() (*PermissionService, *store.MemStore) {
|
||||
ms := store.NewMemStore()
|
||||
ms.SeedRole(&db.Role{
|
||||
ID: permissions.MemberRoleID,
|
||||
Name: "member",
|
||||
Permissions: permissions.SendMessages | permissions.ReadMessages | permissions.AddReactions,
|
||||
Position: 1,
|
||||
})
|
||||
ms.SeedUserRole(1, permissions.MemberRoleID)
|
||||
checker := permissions.NewChecker(ms)
|
||||
return NewPermissionService(ms, checker), ms
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_Allowed(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// Member has SendMessages | ReadMessages; no overrides exist, so base role perms apply.
|
||||
if !svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected user to have SendMessages permission")
|
||||
}
|
||||
if !svc.HasChannelPerm(1, 10, permissions.ReadMessages) {
|
||||
t.Fatal("expected user to have ReadMessages permission")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_Denied(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// ManageMessages is NOT in the member role.
|
||||
if svc.HasChannelPerm(1, 10, permissions.ManageMessages) {
|
||||
t.Fatal("expected user to NOT have ManageMessages permission")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_OverrideDeny(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "readonly", Type: "text"})
|
||||
// Deny SendMessages for this channel.
|
||||
ms.SeedChannelOverride(permissions.MemberRoleID, 10, 0, permissions.SendMessages)
|
||||
// Invalidate so next check re-populates cache.
|
||||
svc.InvalidateAll()
|
||||
|
||||
if svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected SendMessages to be denied via channel override")
|
||||
}
|
||||
// ReadMessages should still be allowed.
|
||||
if !svc.HasChannelPerm(1, 10, permissions.ReadMessages) {
|
||||
t.Fatal("expected ReadMessages to remain allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_OverrideAllow(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "special", Type: "text"})
|
||||
// Allow ManageMessages (not in base role) via channel override.
|
||||
ms.SeedChannelOverride(permissions.MemberRoleID, 10, permissions.ManageMessages, 0)
|
||||
svc.InvalidateAll()
|
||||
|
||||
if !svc.HasChannelPerm(1, 10, permissions.ManageMessages) {
|
||||
t.Fatal("expected ManageMessages to be allowed via channel override")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_AdminBypass(t *testing.T) {
|
||||
ms := store.NewMemStore()
|
||||
ms.SeedRole(&db.Role{
|
||||
ID: permissions.AdminRoleID,
|
||||
Name: "admin",
|
||||
Permissions: permissions.Administrator,
|
||||
Position: 90,
|
||||
})
|
||||
ms.SeedUserRole(1, permissions.AdminRoleID)
|
||||
checker := permissions.NewChecker(ms)
|
||||
svc := NewPermissionService(ms, checker)
|
||||
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "locked", Type: "text"})
|
||||
// Deny everything via override; admin should still bypass.
|
||||
ms.SeedChannelOverride(permissions.AdminRoleID, 10, 0, permissions.SendMessages|permissions.ReadMessages)
|
||||
|
||||
if !svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("admin should bypass all permission checks")
|
||||
}
|
||||
if !svc.HasChannelPerm(1, 10, permissions.ManageMessages) {
|
||||
t.Fatal("admin should bypass all permission checks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidateUser_ClearsCacheForUser(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// Populate cache.
|
||||
svc.HasChannelPerm(1, 10, permissions.SendMessages)
|
||||
|
||||
// Now add a deny override.
|
||||
ms.SeedChannelOverride(permissions.MemberRoleID, 10, 0, permissions.SendMessages)
|
||||
|
||||
// Without invalidation, cache still says allowed.
|
||||
if !svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected cached value to still allow SendMessages")
|
||||
}
|
||||
|
||||
// After invalidation, should pick up the override.
|
||||
svc.InvalidateUser(1)
|
||||
if svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected SendMessages to be denied after cache invalidation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidateAll_ClearsEntireCache(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
// Add a second user.
|
||||
ms.SeedUserRole(2, permissions.MemberRoleID)
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// Populate cache for both users.
|
||||
svc.HasChannelPerm(1, 10, permissions.SendMessages)
|
||||
svc.HasChannelPerm(2, 10, permissions.SendMessages)
|
||||
|
||||
// Add deny override.
|
||||
ms.SeedChannelOverride(permissions.MemberRoleID, 10, 0, permissions.SendMessages)
|
||||
|
||||
// Both still cached as allowed.
|
||||
if !svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected cached allow for user 1")
|
||||
}
|
||||
if !svc.HasChannelPerm(2, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected cached allow for user 2")
|
||||
}
|
||||
|
||||
svc.InvalidateAll()
|
||||
|
||||
// Both should now see the deny.
|
||||
if svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected deny for user 1 after InvalidateAll")
|
||||
}
|
||||
if svc.HasChannelPerm(2, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected deny for user 2 after InvalidateAll")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermCacheTTLExpiry(t *testing.T) {
|
||||
// This test verifies the cache TTL mechanism. We cannot easily wait 30s
|
||||
// in a unit test, so we verify the structural behavior: after manually
|
||||
// backdating the populatedAt field the cache should be stale and the
|
||||
// next check should re-populate from the store.
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// Populate cache.
|
||||
svc.HasChannelPerm(1, 10, permissions.SendMessages)
|
||||
|
||||
// Add deny override.
|
||||
ms.SeedChannelOverride(permissions.MemberRoleID, 10, 0, permissions.SendMessages)
|
||||
|
||||
// Manually expire the cache entry by backdating populatedAt.
|
||||
svc.mu.Lock()
|
||||
if cp, ok := svc.cache[int64(1)]; ok {
|
||||
cp.populatedAt = time.Now().Add(-permCacheTTL - time.Second)
|
||||
}
|
||||
svc.mu.Unlock()
|
||||
|
||||
// The next call should re-populate and pick up the deny.
|
||||
if svc.HasChannelPerm(1, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected cache TTL expiry to cause re-population with deny override")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasChannelPerm_UnknownUserReturnsFalse(t *testing.T) {
|
||||
svc, ms := newTestPermService()
|
||||
ms.SeedChannel(&db.Channel{ID: 10, Name: "general", Type: "text"})
|
||||
|
||||
// User 999 has no role assigned.
|
||||
if svc.HasChannelPerm(999, 10, permissions.SendMessages) {
|
||||
t.Fatal("expected false for unknown user")
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ type Services struct {
|
||||
DMs *DMService
|
||||
Invites *InviteService
|
||||
Blocks *BlockService
|
||||
Moderation *ModerationService
|
||||
Voice *VoiceService
|
||||
}
|
||||
|
||||
// New creates all domain services wired together.
|
||||
@@ -34,5 +36,7 @@ func New(st store.Store, limiter *auth.RateLimiter) *Services {
|
||||
DMs: NewDMService(st),
|
||||
Invites: NewInviteService(st),
|
||||
Blocks: NewBlockService(st),
|
||||
Moderation: NewModerationService(st),
|
||||
Voice: NewVoiceService(st, permSvc),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/owncord/server/db"
|
||||
"github.com/owncord/server/permissions"
|
||||
"github.com/owncord/server/store"
|
||||
)
|
||||
|
||||
// VoiceService handles voice state business logic.
|
||||
type VoiceService struct {
|
||||
st store.Store
|
||||
perm *PermissionService
|
||||
}
|
||||
|
||||
// NewVoiceService creates a VoiceService.
|
||||
func NewVoiceService(st store.Store, perm *PermissionService) *VoiceService {
|
||||
return &VoiceService{st: st, perm: perm}
|
||||
}
|
||||
|
||||
// JoinChannel validates the channel, checks ConnectVoice permission, and
|
||||
// joins the user to the voice channel respecting capacity limits.
|
||||
// Returns the channel on success so callers can access voice config fields.
|
||||
func (s *VoiceService) JoinChannel(userID, channelID int64) (*db.Channel, error) {
|
||||
if channelID <= 0 {
|
||||
return nil, fmt.Errorf("%w: channel_id must be a positive integer", ErrBadRequest)
|
||||
}
|
||||
|
||||
ch, err := s.st.GetChannel(channelID)
|
||||
if err != nil || ch == nil {
|
||||
return nil, fmt.Errorf("%w: channel not found", ErrNotFound)
|
||||
}
|
||||
|
||||
if !s.perm.HasChannelPerm(userID, channelID, permissions.ConnectVoice) {
|
||||
return nil, fmt.Errorf("%w: missing CONNECT_VOICE permission", ErrForbidden)
|
||||
}
|
||||
|
||||
maxUsers := ch.VoiceMaxUsers
|
||||
if maxUsers > 0 {
|
||||
if err := s.st.JoinVoiceChannelIfCapacity(userID, channelID, maxUsers); err != nil {
|
||||
if errors.Is(err, db.ErrChannelFull) {
|
||||
return nil, fmt.Errorf("%w: voice channel is full", ErrForbidden)
|
||||
}
|
||||
slog.Error("VoiceService.JoinChannel JoinVoiceChannelIfCapacity", "err", err, "user_id", userID)
|
||||
return nil, fmt.Errorf("%w: failed to join voice channel", ErrInternal)
|
||||
}
|
||||
} else {
|
||||
if err := s.st.JoinVoiceChannel(userID, channelID); err != nil {
|
||||
slog.Error("VoiceService.JoinChannel JoinVoiceChannel", "err", err, "user_id", userID)
|
||||
return nil, fmt.Errorf("%w: failed to join voice channel", ErrInternal)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("voice join", "user_id", userID, "channel_id", channelID)
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// LeaveChannel removes the user from their current voice channel.
|
||||
func (s *VoiceService) LeaveChannel(userID int64) error {
|
||||
if err := s.st.LeaveVoiceChannel(userID); err != nil {
|
||||
slog.Error("VoiceService.LeaveChannel", "err", err, "user_id", userID)
|
||||
return fmt.Errorf("%w: failed to leave voice channel", ErrInternal)
|
||||
}
|
||||
|
||||
slog.Info("voice leave", "user_id", userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateMute toggles the mute state for the given user.
|
||||
func (s *VoiceService) UpdateMute(userID int64, muted bool) error {
|
||||
if err := s.st.UpdateVoiceMute(userID, muted); err != nil {
|
||||
slog.Error("VoiceService.UpdateMute", "err", err, "user_id", userID)
|
||||
return fmt.Errorf("%w: failed to update mute state", ErrInternal)
|
||||
}
|
||||
|
||||
slog.Debug("voice mute changed", "user_id", userID, "muted", muted)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDeafen toggles the deafen state for the given user.
|
||||
func (s *VoiceService) UpdateDeafen(userID int64, deafened bool) error {
|
||||
if err := s.st.UpdateVoiceDeafen(userID, deafened); err != nil {
|
||||
slog.Error("VoiceService.UpdateDeafen", "err", err, "user_id", userID)
|
||||
return fmt.Errorf("%w: failed to update deafen state", ErrInternal)
|
||||
}
|
||||
|
||||
slog.Debug("voice deafen changed", "user_id", userID, "deafened", deafened)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleCamera enables or disables the user's camera. When enabling, it
|
||||
// enforces the maxVideo limit via an atomic check-and-update. Returns true
|
||||
// if the camera was successfully enabled (or disabled), false if the video
|
||||
// limit was reached.
|
||||
func (s *VoiceService) ToggleCamera(userID, channelID int64, enable bool, maxVideo int) (bool, error) {
|
||||
if !s.perm.HasChannelPerm(userID, channelID, permissions.UseVideo) {
|
||||
return false, fmt.Errorf("%w: missing USE_VIDEO permission", ErrForbidden)
|
||||
}
|
||||
|
||||
if enable && maxVideo > 0 {
|
||||
ok, err := s.st.EnableCameraIfUnderLimit(userID, channelID, maxVideo)
|
||||
if err != nil {
|
||||
slog.Error("VoiceService.ToggleCamera EnableCameraIfUnderLimit", "err", err, "user_id", userID)
|
||||
return false, fmt.Errorf("%w: failed to check video limit", ErrInternal)
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
if err := s.st.UpdateVoiceCamera(userID, enable); err != nil {
|
||||
slog.Error("VoiceService.ToggleCamera UpdateVoiceCamera", "err", err, "user_id", userID)
|
||||
return false, fmt.Errorf("%w: failed to update camera state", ErrInternal)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Debug("voice camera changed", "user_id", userID, "enabled", enable, "channel_id", channelID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ToggleScreenshare enables or disables the user's screen share after
|
||||
// checking the ShareScreen permission.
|
||||
func (s *VoiceService) ToggleScreenshare(userID, channelID int64, enable bool) error {
|
||||
if !s.perm.HasChannelPerm(userID, channelID, permissions.ShareScreen) {
|
||||
return fmt.Errorf("%w: missing SHARE_SCREEN permission", ErrForbidden)
|
||||
}
|
||||
|
||||
if err := s.st.UpdateVoiceScreenshare(userID, enable); err != nil {
|
||||
slog.Error("VoiceService.ToggleScreenshare", "err", err, "user_id", userID)
|
||||
return fmt.Errorf("%w: failed to update screenshare state", ErrInternal)
|
||||
}
|
||||
|
||||
slog.Debug("voice screenshare changed", "user_id", userID, "enabled", enable, "channel_id", channelID)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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),
|
||||
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
|
||||
}
|
||||
|
||||
func (m *MemStore) LinkAttachmentsToMessage(_ int64, _ []string) (int64, error) {
|
||||
return 0, 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(_ int64, _ string) error {
|
||||
panic("memstore: not implemented: UpdateUserPassword")
|
||||
}
|
||||
|
||||
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(_ int64, _ int64) (*db.User, error) {
|
||||
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(_ string, _ int64, _, _, _ string, _ int64, _, _ *int) error {
|
||||
panic("memstore: not implemented: CreateAttachment")
|
||||
}
|
||||
|
||||
func (m *MemStore) GetAttachmentByID(_ string) (*db.Attachment, error) {
|
||||
panic("memstore: not implemented: GetAttachmentByID")
|
||||
}
|
||||
|
||||
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(_ int64, _ string, _ *time.Time) error {
|
||||
panic("memstore: not implemented: BanUser")
|
||||
}
|
||||
|
||||
func (m *MemStore) UnbanUser(_ int64) error {
|
||||
panic("memstore: not implemented: UnbanUser")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user