Files
OwnCord/Server/service/datastore.go
T
J3vb db0275a290 fix: batch of 29 correctness fixes across server and client (#1369)
* fix(ws): 2 defect(s) (OC-0013, OC-0140)

* fix(voice): 1 defect(s) (OC-0044)

* fix(ws): 1 defect(s) (OC-0024)

* fix(server): 1 defect(s) (OC-0027)

* fix(ws): 1 defect(s) (OC-0028)

* fix(server): 7 defect(s) (OC-0033, OC-0066, OC-0067, OC-0068, OC-0074, OC-0077, OC-0106)

* fix(ws): 1 defect(s) (OC-0051)

* fix(client): 1 defect(s) (OC-0053)

* fix(client): 1 defect(s) (OC-0055)

* fix(service): 1 defect(s) (OC-0069)

* fix(voice): 1 defect(s) (OC-0072)

* fix(service): 1 defect(s) (OC-0082)

* fix(client): 1 defect(s) (OC-0083)

* fix(plugin): 1 defect(s) (OC-0088)

* fix(plugin): 4 defect(s) (OC-0104, OC-0126, OC-0127, OC-0133)

* fix(admin): 1 defect(s) (OC-0110)

* fix(client): 1 defect(s) (OC-0114)

* fix(api): 1 defect(s) (OC-0139)

* fix(client): 1 defect(s) (OC-0149)

* test(server): adapt existing tests to updated OpenDM and IncrementMentionCounts signatures

* style(plugin): modernize loops and goroutine spawns in race test

* fix(ws): mirror the focus admission gate in the post-subscribe revalidation

* fix(service): detach DM post-commit side effects from the request ctx, fail delete closed, add empty-fan-out fallback

* fix(plugin): preserve enabled intent when upgrade reactivation hits a runtime-less build

* chore(skills): harden bughunt-fix workflow and fold review lessons into bughunt-run/db-change

* Add comprehensive documentation for task-observer skill

- Introduced environments.md to outline activation setup, compaction behavior, and handoff-doc mode.
- Created skill-authoring.md detailing taxonomy, licensing, confidentiality, and editing rules for skill creation.
- Added weekly-review.md for a structured review process of OPEN observations, including scheduled and in-session fallback modes.

* chore(go): pin toolchain go1.26.6 (stdlib CVE fixes flagged by govulncheck)
2026-08-14 10:05:40 +02:00

202 lines
13 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(ctx context.Context, channelID, userID int64, content string, replyTo *int64) (int64, error)
CreateMessageReturning(ctx context.Context, channelID, userID int64, content string, replyTo *int64) (*db.Message, error)
CreateMessageWithMentions(ctx context.Context, channelID, userID int64, content string, replyTo *int64, mentionedUserIDs []int64, mentionsEveryone bool) (*db.Message, error)
GetMessage(ctx context.Context, id int64) (*db.Message, error)
GetMessages(ctx context.Context, channelID, before int64, limit int) ([]db.MessageWithUser, error)
GetMessagesForAPI(ctx context.Context, channelID, before int64, limit int, requestingUserID int64) ([]db.MessageAPIResponse, error)
GetMessagesAroundForAPI(ctx context.Context, channelID, centerID int64, beforeCount, afterCount int, requestingUserID int64) ([]db.MessageAPIResponse, error)
EditMessage(ctx context.Context, id, userID int64, content string) (*db.Message, error)
DeleteMessage(ctx context.Context, id, userID int64, isMod bool) error
PurgeChannelMessages(ctx context.Context, channelID, before int64, limit int) ([]int64, error)
SearchMessages(ctx context.Context, query string, channelID *int64, limit int) ([]db.MessageSearchResult, error)
SearchMessagesInChannels(ctx context.Context, query string, channelIDs []int64, limit int) ([]db.MessageSearchResult, error)
GetPinnedMessages(ctx context.Context, channelID int64, requestingUserID int64) ([]db.MessageAPIResponse, error)
SetMessagePinned(ctx context.Context, id int64, pinned bool) error
AddReaction(ctx context.Context, messageID, userID int64, emoji string) error
RemoveReaction(ctx context.Context, messageID, userID int64, emoji string) error
GetReactions(ctx context.Context, messageID int64) ([]db.ReactionCount, error)
GetReactionUsers(ctx context.Context, messageID int64, emoji string, limit int) ([]db.ReactionUser, error)
UpdateReadState(ctx context.Context, userID, channelID, lastReadMessageID int64) error
GetChannelUnreadCounts(ctx context.Context, userID int64) (map[int64]db.ChannelUnread, error)
// ── Mentions ──
ReplaceMessageMentions(ctx context.Context, messageID int64, mentionedUserIDs []int64, mentionsEveryone bool) error
GetMentionsByMessageIDs(ctx context.Context, msgIDs []int64) (map[int64][]int64, error)
IncrementMentionCounts(ctx context.Context, channelID, msgID int64, userIDs []int64) error
GetUserIDsByUsernames(ctx context.Context, usernames []string) (map[string]int64, error)
ListMentionTargetsByRoles(ctx context.Context, roleIDs []int64) ([]db.MentionTarget, error)
ListBlockersOf(ctx context.Context, blockedID int64) ([]int64, error)
GetChannelOverrides(ctx context.Context, channelID int64) (map[int64]db.ChannelOverride, error)
GetChannelUserOverrides(ctx context.Context, channelID int64) (map[int64]db.ChannelOverride, error)
ListMentionTargetsByUserIDs(ctx context.Context, userIDs []int64) ([]db.MentionTarget, error)
GetLatestMessageID(ctx context.Context, channelID int64) (int64, error)
LinkAttachmentsToMessage(ctx context.Context, messageID, uploaderID int64, attachmentIDs []string) (int64, error)
GetAttachmentsByMessageIDs(ctx context.Context, msgIDs []int64) (map[int64][]db.AttachmentInfo, error)
// ── Channels ──
ListChannels(ctx context.Context) ([]db.Channel, error)
GetChannel(ctx context.Context, id int64) (*db.Channel, error)
CreateChannel(ctx context.Context, name, chanType, category, topic string, position int) (int64, error)
UpdateChannel(ctx context.Context, id int64, name, topic string, slowMode int) error
DeleteChannel(ctx context.Context, id int64) error
SetChannelSlowMode(ctx context.Context, id int64, slowMode int) error
SetChannelVoiceMaxUsers(ctx context.Context, id int64, maxUsers int) error
// GetChannelPermissions / GetUserChannelPermissions are the two single-row
// override lookups permissions.DB requires (Store is passed straight to
// permissions.NewChecker).
GetChannelPermissions(ctx context.Context, channelID, roleID int64) (allow, deny int64, err error)
GetUserChannelPermissions(ctx context.Context, channelID, userID int64) (allow, deny int64, err error)
GetAllChannelPermissionsForRole(ctx context.Context, roleID int64) (map[int64]db.ChannelOverride, error)
// GetChannelOverridesFor merges the role and per-user override layers for
// one member in two batch queries — the single fetch behind every
// "what can this member do here" site, and the reason no site pays an N+1
// for the second layer.
GetChannelOverridesFor(ctx context.Context, roleID, userID int64) (map[int64]db.ChannelOverride, error)
GetChannelTypes(ctx context.Context, ids []int64) (map[int64]string, error)
// ── Users ──
GetUserByID(ctx context.Context, id int64) (*db.User, error)
GetUserByUsername(ctx context.Context, username string) (*db.User, error)
CreateUser(ctx context.Context, username, passwordHash string, roleID int) (int64, error)
CreateOwnerIfEmpty(ctx context.Context, username, passwordHash string, roleID int) (int64, error)
CreateUserWithInvite(ctx context.Context, username, passwordHash string, roleID int, inviteCode string) (int64, error)
UpdateUserProfile(ctx context.Context, userID int64, username string, avatar, displayName, about *string) error
UpdateUserCustomStatus(ctx context.Context, userID int64, customStatus *string) error
UpdateUserPassword(ctx context.Context, userID int64, newPasswordHash string) error
UpdateUserStatus(ctx context.Context, id int64, status string) error
UpdateUserTOTPSecret(ctx context.Context, id int64, secret *string) error
UpdateUserIdentityKey(ctx context.Context, id int64, key *string) error
UpdateUserRole(ctx context.Context, userID, roleID int64) error
ResetAllUserStatuses(ctx context.Context) error
DeleteAccount(ctx context.Context, userID int64) error
ListMembers(ctx context.Context) ([]db.MemberSummary, error)
// ── Sessions ──
CreateSession(ctx context.Context, userID int64, tokenHash, device, ip string) (int64, error)
GetSessionByTokenHash(ctx context.Context, tokenHash string) (*db.Session, error)
GetSessionWithBanStatus(ctx context.Context, tokenHash string) (*db.SessionWithBanStatus, error)
DeleteSession(ctx context.Context, tokenHash string) error
DeleteOtherSessions(ctx context.Context, userID, keepSessionID int64) (int64, error)
DeleteExpiredSessions(ctx context.Context) error
DeleteSessionByID(ctx context.Context, sessionID, userID int64) error
TouchSession(ctx context.Context, tokenHash string) error
ListUserSessions(ctx context.Context, userID int64) ([]db.Session, error)
ForceLogoutUser(ctx context.Context, userID int64) error
GetUserSessions(ctx context.Context, userID int64) ([]db.Session, error)
// ── Roles ──
GetRoleByID(ctx context.Context, id int64) (*db.Role, error)
GetRoleForUser(ctx context.Context, userID int64) (*db.Role, error)
GetUserWithRole(ctx context.Context, userID int64) (*db.User, *db.Role, error)
ListRoles(ctx context.Context) ([]*db.Role, error)
GetRoleByName(ctx context.Context, name string) (*db.Role, error)
GetDefaultRole(ctx context.Context) (*db.Role, error)
CreateRole(ctx context.Context, name string, color *string, perms int64, position int) (*db.Role, error)
UpdateRole(ctx context.Context, id int64, name string, color *string, perms int64, position int) error
SetRolePositions(ctx context.Context, positions map[int64]int) error
DeleteRoleReassigning(ctx context.Context, roleID, fallbackRoleID int64) ([]int64, error)
ListUserIDsByRole(ctx context.Context, roleID int64) ([]int64, error)
CountRoleMembers(ctx context.Context) (map[int64]int, error)
// ── Emoji ──
ListEmoji(ctx context.Context) ([]*db.Emoji, error)
GetEmoji(ctx context.Context, id int64) (*db.Emoji, error)
GetEmojiByShortcode(ctx context.Context, shortcode string) (*db.Emoji, error)
CreateEmoji(ctx context.Context, shortcode, storedAs, mimeType string, uploadedBy int64) (*db.Emoji, error)
DeleteEmoji(ctx context.Context, id int64) (bool, error)
// ── Invites ──
CreateInvite(ctx context.Context, createdBy int64, maxUses int, expiresAt *time.Time) (string, error)
GetInvite(ctx context.Context, code string) (*db.Invite, error)
ListInvites(ctx context.Context) ([]*db.Invite, error)
UseInviteAtomic(ctx context.Context, code string) error
RevokeInvite(ctx context.Context, code string) error
// ── Voice ──
JoinVoiceChannel(ctx context.Context, userID, channelID int64) error
JoinVoiceChannelIfCapacity(ctx context.Context, userID, channelID int64, maxUsers int) error
LeaveVoiceChannel(ctx context.Context, userID int64) error
LeaveVoiceChannelIfMatch(ctx context.Context, userID, expectedChannelID int64, expectedJoinedAt string) (bool, error)
GetVoiceState(ctx context.Context, userID int64) (*db.VoiceState, error)
GetChannelVoiceStates(ctx context.Context, channelID int64) ([]db.VoiceState, error)
GetAllVoiceStates(ctx context.Context) ([]db.VoiceState, error)
UpdateVoiceMute(ctx context.Context, userID int64, muted bool) error
UpdateVoiceDeafen(ctx context.Context, userID int64, deafened bool) error
ClearVoiceState(ctx context.Context, userID int64) error
ClearAllVoiceStates(ctx context.Context) error
CountActiveCameras(ctx context.Context, channelID int64) (int, error)
UpdateVoiceCamera(ctx context.Context, userID int64, camera bool) error
EnableCameraIfUnderLimit(ctx context.Context, userID, channelID int64, maxVideo int) (bool, error)
UpdateVoiceScreenshare(ctx context.Context, userID int64, screenshare bool) error
CountChannelVoiceUsers(ctx context.Context, channelID int64) (int, error)
// ── Direct messages ──
GetOrCreateDMChannel(ctx context.Context, user1ID, user2ID int64) (*db.Channel, bool, error)
GetUserDMChannels(ctx context.Context, userID int64) ([]db.DMChannelInfo, error)
GetUserDMChannelIDs(ctx context.Context, userID int64) ([]int64, error)
OpenDM(ctx context.Context, userID, channelID int64) (bool, error)
CloseDM(ctx context.Context, userID, channelID int64) error
IsDMParticipant(ctx context.Context, userID, channelID int64) (bool, error)
GetDMParticipantIDs(ctx context.Context, channelID int64) ([]int64, error)
GetDMRecipient(ctx context.Context, channelID, requestingUserID int64) (*db.User, error)
CreateGroupDMChannel(ctx context.Context, name string, participantIDs []int64) (*db.Channel, error)
LeaveGroupDM(ctx context.Context, userID, channelID int64) (bool, error)
CountDMParticipants(ctx context.Context, channelID int64) (int, error)
IsGroupDM(ctx context.Context, channelID int64) (bool, error)
SetDMChannelName(ctx context.Context, channelID int64, name string) error
GetDMParticipants(ctx context.Context, channelID, viewerID int64) ([]db.DMUser, error)
// ── Blocks ──
BlockUser(ctx context.Context, blockerID, blockedID int64) error
UnblockUser(ctx context.Context, blockerID, blockedID int64) error
IsBlocked(ctx context.Context, blockerID, blockedID int64) (bool, error)
IsEitherBlocked(ctx context.Context, userA, userB int64) (bool, error)
ListBlockedUsers(ctx context.Context, blockerID int64) ([]int64, error)
// ── Attachments ──
CreateAttachment(ctx context.Context, id string, uploaderID int64, filename, storedAs, mimeType string, size int64, width, height *int) error
GetAttachmentByID(ctx context.Context, id string) (*db.Attachment, error)
GetAttachmentWithChannel(ctx context.Context, id string) (*db.AttachmentAccess, error)
DeleteOrphanedAttachments(ctx context.Context, cutoff time.Time) ([]string, error)
// ── Admin ──
UserCount(ctx context.Context) (int64, error)
GetServerStats(ctx context.Context) (*db.ServerStats, error)
ListAllUsers(ctx context.Context, limit, offset int) ([]db.UserWithRole, error)
BanUser(ctx context.Context, id int64, reason string, expires *time.Time) error
UnbanUser(ctx context.Context, id int64) error
LogAudit(ctx context.Context, actorID int64, action, targetType string, targetID int64, detail string) error
GetAuditLog(ctx context.Context, limit, offset int) ([]db.AuditEntry, error)
AdminCreateChannel(ctx context.Context, name, chanType, category, topic string, position int) (int64, error)
AdminUpdateChannel(ctx context.Context, id int64, u db.ChannelUpdate) error
AdminDeleteChannel(ctx context.Context, id int64) error
BackupTo(ctx context.Context, path string) error
BackupToSafe(ctx context.Context, path, safeRoot string) error
CountUsersWithoutTOTP(ctx context.Context) (int, error)
// ── Settings ──
GetSetting(ctx context.Context, key string) (string, error)
SetSetting(ctx context.Context, key, value string) error
GetAllSettings(ctx context.Context) (map[string]string, error)
}