Files
OwnCord/Server/service/datastore.go
T
Claude 5f1d6fc287 refactor(server): remove the store abstraction layer (D3)
Deletes Server/store (SQLiteStore, MemStore, the composed Store
interface) and collapses to a single sqlc-backed db package, executing
the prior audit's P4 "single data layer" direction (finding #6).

SQLiteStore was a pure pass-through to *db.DB, so consumers now depend
on narrow interfaces that *db.DB satisfies directly:

  - service.Store   (service/datastore.go, renamed from store/store.go)
  - ws.EventStore   (ws/eventstore.go)
  - plugin.PluginStore (plugin/pluginstore.go)

The event- and plugin-KV methods that lived in the store's SQLite
implementation move into the db package (db/event_queries.go,
db/plugin_queries.go), keeping their raw-SQL form.

Tests: the MemStore-based unit tests now run against a real in-memory
SQLite db opened per-test with migrations applied, via package-local
seed helpers. Fault-injection tests embed a real *db.DB and override the
single method under test, preserving error-path coverage. Full server
suite and sqlc-verify are green.

Docs: audit finding #6 and A-2026-07-06 marked resolved; decisions D3
updated; architecture server.md / data-model.md diagrams and prose
updated to the api -> service -> db layering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 16:33:58 +00:00

153 lines
7.1 KiB
Go

package service
import (
"context"
"time"
"github.com/owncord/server/db"
)
// Store is the data-access surface the service layer depends on. It is the
// set of *db.DB methods the services call — no event/plugin/transaction
// methods, which belong to other layers. *db.DB satisfies this interface
// directly (D3 removed the former store package's pass-through wrapper), and
// tests inject fakes that embed a real in-memory *db.DB and override the one
// method they need to exercise an error path.
//
// permissions.NewChecker takes its own narrower interface (permissions.DB),
// which *db.DB and this Store both satisfy.
type Store interface {
// ── Messages / reactions / read-state ──
CreateMessage(channelID, userID int64, content string, replyTo *int64) (int64, error)
GetMessage(id int64) (*db.Message, error)
GetMessages(channelID, before int64, limit int) ([]db.MessageWithUser, error)
GetMessagesForAPI(channelID, before int64, limit int, requestingUserID int64) ([]db.MessageAPIResponse, error)
EditMessage(id, userID int64, content string) error
DeleteMessage(id, userID int64, isMod bool) error
SearchMessages(query string, channelID *int64, limit int) ([]db.MessageSearchResult, error)
SearchMessagesInChannels(query string, channelIDs []int64, limit int) ([]db.MessageSearchResult, error)
GetPinnedMessages(channelID int64, requestingUserID int64) ([]db.MessageAPIResponse, error)
SetMessagePinned(id int64, pinned bool) error
AddReaction(messageID, userID int64, emoji string) error
RemoveReaction(messageID, userID int64, emoji string) error
GetReactions(messageID int64) ([]db.ReactionCount, error)
UpdateReadState(userID, channelID, lastReadMessageID int64) error
GetChannelUnreadCounts(userID int64) (map[int64]db.ChannelUnread, error)
GetLatestMessageID(channelID int64) (int64, error)
LinkAttachmentsToMessage(messageID, uploaderID int64, attachmentIDs []string) (int64, error)
GetAttachmentsByMessageIDs(msgIDs []int64) (map[int64][]db.AttachmentInfo, error)
// ── Channels ──
ListChannels() ([]db.Channel, error)
GetChannel(id int64) (*db.Channel, error)
CreateChannel(name, chanType, category, topic string, position int) (int64, error)
UpdateChannel(id int64, name, topic string, slowMode int) error
DeleteChannel(id int64) error
SetChannelSlowMode(id int64, slowMode int) error
SetChannelVoiceMaxUsers(id int64, maxUsers int) error
GetChannelPermissions(channelID, roleID int64) (allow, deny int64, err error)
GetAllChannelPermissionsForRole(roleID int64) (map[int64]db.ChannelOverride, error)
GetChannelTypes(ids []int64) (map[int64]string, error)
// ── Users ──
GetUserByID(id int64) (*db.User, error)
GetUserByUsername(username string) (*db.User, error)
CreateUser(username, passwordHash string, roleID int) (int64, error)
CreateOwnerIfEmpty(username, passwordHash string, roleID int) (int64, error)
CreateUserWithInvite(username, passwordHash string, roleID int, inviteCode string) (int64, error)
UpdateUserProfile(userID int64, username string, avatar *string) error
UpdateUserPassword(userID int64, newPasswordHash string) error
UpdateUserStatus(id int64, status string) error
UpdateUserTOTPSecret(id int64, secret *string) error
UpdateUserRole(userID, roleID int64) error
ResetAllUserStatuses() error
DeleteAccount(ctx context.Context, userID int64) error
ListMembers() ([]db.MemberSummary, error)
// ── Sessions ──
CreateSession(userID int64, tokenHash, device, ip string) (int64, error)
GetSessionByTokenHash(tokenHash string) (*db.Session, error)
GetSessionWithBanStatus(tokenHash string) (*db.SessionWithBanStatus, error)
DeleteSession(tokenHash string) error
DeleteOtherSessions(userID, keepSessionID int64) (int64, error)
DeleteExpiredSessions() error
DeleteSessionByID(sessionID, userID int64) error
TouchSession(tokenHash string) error
ListUserSessions(userID int64) ([]db.Session, error)
ForceLogoutUser(userID int64) error
GetUserSessions(userID int64) ([]db.Session, error)
// ── Roles ──
GetRoleByID(id int64) (*db.Role, error)
GetRoleForUser(userID int64) (*db.Role, error)
GetUserWithRole(userID int64) (*db.User, *db.Role, error)
ListRoles() ([]*db.Role, error)
// ── Invites ──
CreateInvite(createdBy int64, maxUses int, expiresAt *time.Time) (string, error)
GetInvite(code string) (*db.Invite, error)
ListInvites() ([]*db.Invite, error)
UseInviteAtomic(code string) error
RevokeInvite(code string) error
// ── Voice ──
JoinVoiceChannel(userID, channelID int64) error
JoinVoiceChannelIfCapacity(userID, channelID int64, maxUsers int) error
LeaveVoiceChannel(userID int64) error
LeaveVoiceChannelIfMatch(userID, expectedChannelID int64, expectedJoinedAt string) (bool, error)
GetVoiceState(userID int64) (*db.VoiceState, error)
GetChannelVoiceStates(channelID int64) ([]db.VoiceState, error)
GetAllVoiceStates() ([]db.VoiceState, error)
UpdateVoiceMute(userID int64, muted bool) error
UpdateVoiceDeafen(userID int64, deafened bool) error
ClearVoiceState(userID int64) error
ClearAllVoiceStates() error
CountActiveCameras(channelID int64) (int, error)
UpdateVoiceCamera(userID int64, camera bool) error
EnableCameraIfUnderLimit(userID, channelID int64, maxVideo int) (bool, error)
UpdateVoiceScreenshare(userID int64, screenshare bool) error
CountChannelVoiceUsers(channelID int64) (int, error)
// ── Direct messages ──
GetOrCreateDMChannel(user1ID, user2ID int64) (*db.Channel, bool, error)
GetUserDMChannels(userID int64) ([]db.DMChannelInfo, error)
OpenDM(userID, channelID int64) error
CloseDM(userID, channelID int64) error
IsDMParticipant(userID, channelID int64) (bool, error)
GetDMParticipantIDs(channelID int64) ([]int64, error)
GetDMRecipient(channelID, requestingUserID int64) (*db.User, error)
// ── Blocks ──
BlockUser(blockerID, blockedID int64) error
UnblockUser(blockerID, blockedID int64) error
IsBlocked(blockerID, blockedID int64) (bool, error)
IsEitherBlocked(userA, userB int64) (bool, error)
ListBlockedUsers(blockerID int64) ([]int64, error)
// ── Attachments ──
CreateAttachment(id string, uploaderID int64, filename, storedAs, mimeType string, size int64, width, height *int) error
GetAttachmentByID(id string) (*db.Attachment, error)
GetAttachmentWithChannel(id string) (*db.AttachmentAccess, error)
DeleteOrphanedAttachments(cutoff string) ([]string, error)
// ── Admin ──
UserCount() (int64, error)
GetServerStats() (*db.ServerStats, error)
ListAllUsers(limit, offset int) ([]db.UserWithRole, error)
BanUser(id int64, reason string, expires *time.Time) error
UnbanUser(id int64) error
LogAudit(actorID int64, action, targetType string, targetID int64, detail string) error
GetAuditLog(limit, offset int) ([]db.AuditEntry, error)
AdminCreateChannel(name, chanType, category, topic string, position int) (int64, error)
AdminUpdateChannel(id int64, name, topic string, slowMode, position int, archived bool) error
AdminDeleteChannel(id int64) error
BackupTo(path string) error
BackupToSafe(path, safeRoot string) error
CountUsersWithoutTOTP() (int, error)
// ── Settings ──
GetSetting(key string) (string, error)
SetSetting(key, value string) error
GetAllSettings() (map[string]string, error)
}