// Package adapter contains the interfaces to be implemented by the database adapter package adapter import ( "encoding/json" "time" "github.com/tinode/chat/server/auth" t "github.com/tinode/chat/server/store/types" ) // Adapter is the interface that must be implemented by a database // adapter. The current schema supports a single connection by database type. type Adapter interface { // General // Open and configure the adapter Open(config json.RawMessage) error // Close the adapter Close() error // IsOpen checks if the adapter is ready for use IsOpen() bool // GetDbVersion returns current database version. GetDbVersion() (int, error) // CheckDbVersion checks if the actual database version matches adapter version. CheckDbVersion() error // GetName returns the name of the adapter GetName() string // SetMaxResults configures how many results can be returned in a single DB call. SetMaxResults(val int) error // CreateDb creates the database optionally dropping an existing database first. CreateDb(reset bool) error // UpgradeDb upgrades database to the current adapter version. UpgradeDb() error // Version returns adapter version Version() int // DB connection stats object. Stats() any // User management // UserCreate creates user record UserCreate(user *t.User) error // UserGet returns record for a given user ID UserGet(uid t.Uid) (*t.User, error) // UserGetAll returns user records for a given list of user IDs UserGetAll(ids ...t.Uid) ([]t.User, error) // UserDelete deletes user record UserDelete(uid t.Uid, hard bool) error // UserUpdate updates user record UserUpdate(uid t.Uid, update map[string]any) error // UserUpdateTags adds, removes, or resets user's tags UserUpdateTags(uid t.Uid, add, remove, reset []string) ([]string, error) // UserGetByCred returns user ID for the given validated credential. UserGetByCred(method, value string) (t.Uid, error) // UserUnreadCount returns the total number of unread messages in all topics with // the R permission. If read fails, the counts are still returned with the original // user IDs but with the unread count undefined and non-nil error. UserUnreadCount(ids ...t.Uid) (map[t.Uid]int, error) // UserGetUnvalidated returns a list of no more than 'limit' uids who never logged in, // have no validated credentials and which haven't been updated since 'lastUpdatedBefore'. UserGetUnvalidated(lastUpdatedBefore time.Time, limit int) ([]t.Uid, error) // Credential management // CredUpsert adds or updates a credential record. Returns true if record was inserted, false if updated. CredUpsert(cred *t.Credential) (bool, error) // CredGetActive returns the currently active credential record for the given method. CredGetActive(uid t.Uid, method string) (*t.Credential, error) // CredGetAll returns credential records for the given user and method, validated only or all. CredGetAll(uid t.Uid, method string, validatedOnly bool) ([]t.Credential, error) // CredDel deletes credentials for the given method/value. If method is empty, deletes all // user's credentials. CredDel(uid t.Uid, method, value string) error // CredConfirm marks given credential as validated. CredConfirm(uid t.Uid, method string) error // CredFail increments count of failed validation attepmts for the given credentials. CredFail(uid t.Uid, method string) error // Authentication management for the basic authentication scheme // AuthGetUniqueRecord returns authentication record for a given unique value i.e. login. AuthGetUniqueRecord(unique string) (t.Uid, auth.Level, []byte, time.Time, error) // AuthGetRecord returns authentication record given user ID and method. AuthGetRecord(user t.Uid, scheme string) (string, auth.Level, []byte, time.Time, error) // AuthAddRecord creates new authentication record AuthAddRecord(user t.Uid, scheme, unique string, authLvl auth.Level, secret []byte, expires time.Time) error // AuthDelScheme deletes an existing authentication scheme for the user. AuthDelScheme(user t.Uid, scheme string) error // AuthDelAllRecords deletes all records of a given user. AuthDelAllRecords(uid t.Uid) (int, error) // AuthUpdRecord modifies an authentication record. Only non-default/non-zero values are updated. AuthUpdRecord(user t.Uid, scheme, unique string, authLvl auth.Level, secret []byte, expires time.Time) error // Topic management // TopicCreate creates a topic TopicCreate(topic *t.Topic) error // TopicCreateP2P creates a p2p topic TopicCreateP2P(initiator, invited *t.Subscription) error // TopicGet loads a single topic by name, if it exists. If the topic does not exist the call returns (nil, nil) TopicGet(topic string) (*t.Topic, error) // TopicsForUser loads subscriptions for a given user. Reads public value. // When the 'opts.IfModifiedSince' query is not nil the subscriptions with UpdatedAt > opts.IfModifiedSince // are returned, where UpdatedAt can be either a subscription, a topic, or a user update timestamp. // This is need in order to support paginagion of subscriptions: get subscriptions page by page // from the oldest updates to most recent: // 1. Client already has subscriptions with the latest update timestamp X. // 2. Client asks for N updated subscriptions since X. The server returns N with updates between X and Y. // 3. Client goes to step 1 with X := Y. TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) ([]t.Subscription, error) // UsersForTopic loads users' subscriptions for a given topic. Public is loaded. UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt) ([]t.Subscription, error) // OwnTopics loads a slice of topic names where the user is the owner. OwnTopics(uid t.Uid) ([]string, error) // ChannelsForUser loads a slice of topic names where the user is a channel reader and notifications (P) are enabled. ChannelsForUser(uid t.Uid) ([]string, error) // TopicShare creates topic subscriptions. TopicShare(topic string, subs []*t.Subscription) error // TopicDelete deletes topic, subscriptions, messages. TopicDelete(topic string, isChan, hard bool) error // TopicUpdateOnMessage increments Topic's or User's SeqId value and updates TouchedAt timestamp. TopicUpdateOnMessage(topic string, msg *t.Message) error // TopicUpdateSubCnt refreshes denormalized topic subscriber count. TopicUpdateSubCnt(topic string) error // TopicUpdate updates topic record. TopicUpdate(topic string, update map[string]any) error // TopicOwnerChange updates topic's owner TopicOwnerChange(topic string, newOwner t.Uid) error // Topic subscriptions // SubscriptionGet reads a subscription of a user to a topic SubscriptionGet(topic string, user t.Uid, keepDeleted bool) (*t.Subscription, error) // SubsForUser loads all subscriptions of a given user. Does NOT load Public or Private values, // does not load deleted subscriptions. SubsForUser(user t.Uid) ([]t.Subscription, error) // SubsForTopic gets a list of subscriptions to a given topic.. Does NOT load Public value. SubsForTopic(topic string, keepDeleted bool, opts *t.QueryOpt) ([]t.Subscription, error) // SubsUpdate updates pasrt of a subscription object. Pass nil for fields which don't need to be updated SubsUpdate(topic string, user t.Uid, update map[string]any) error // SubsDelete deletes a single subscription SubsDelete(topic string, user t.Uid) error // Search // Find searches for users or topics given a list of tags. // - caller is the user or topic who is doing the searching, it will be skipped from results. // - prefix if present will cause match rank highest in the results. // - req is a list of required tag sets. Each set is a list of tags. The search will return // all users/topics which have at least one tag from each set. // - opt is a list of optional tags; if present the result will rank higher. // - activeOnly if true will return only active subscriptions. Find(caller, prefix string, req [][]string, opt []string, activeOnly bool) ([]t.Subscription, error) // FindOne returns topic or user which matches the given tag. FindOne(tag string) (string, error) // Messages // MessageSave saves message to database MessageSave(msg *t.Message) error // MessageGetAll returns messages matching the query MessageGetAll(topic string, forUser t.Uid, opts *t.QueryOpt) ([]t.Message, error) // MessageDeleteList marks messages as deleted. // Soft- or Hard- is defined by forUser value: forUser.IsZero == true is hard. MessageDeleteList(topic string, toDel *t.DelMessage) error // MessageGetDeleted returns a list of deleted message Ids. MessageGetDeleted(topic string, forUser t.Uid, opts *t.QueryOpt) ([]t.DelMessage, error) // Devices (for push notifications) // DeviceUpsert creates or updates a device record DeviceUpsert(uid t.Uid, dev *t.DeviceDef) error // DeviceGetAll returns all devices for a given set of users DeviceGetAll(uid ...t.Uid) (map[t.Uid][]t.DeviceDef, int, error) // DeviceDelete deletes a device record DeviceDelete(uid t.Uid, deviceID string) error // File upload records. The files are stored outside of the database. // FileStartUpload initializes a file upload. FileStartUpload(fd *t.FileDef) error // FileFinishUpload marks file upload as completed, successfully or otherwise. FileFinishUpload(fd *t.FileDef, success bool, size int64) (*t.FileDef, error) // FileGet fetches a record of a specific file FileGet(fid string) (*t.FileDef, error) // FileDeleteUnused deletes records where UseCount is zero. If olderThan is non-zero, deletes // unused records with UpdatedAt before olderThan. // Returns array of FileDef.Location of deleted filerecords so actual files can be deleted too. FileDeleteUnused(olderThan time.Time, limit int) ([]string, error) // FileLinkAttachments connects given topic or message to the file record IDs from the list. FileLinkAttachments(topic string, userId, msgId t.Uid, fids []string) error // Persistent cache management. // PCacheGet reads a persistent cache entry. PCacheGet(key string) (string, error) // PCacheUpsert creates or updates a persistent cache entry. PCacheUpsert(key string, value string, failOnDuplicate bool) error // PCacheDelete deletes a single persistent cache entry. PCacheDelete(key string) error // PCacheExpire expires older entries with the specified key prefix. PCacheExpire(keyPrefix string, olderThan time.Time) error // Testing // GetTestDB returns a currently open database connection. GetTestDB() any } // TenantAdapter is an optional capability implemented by tenant-aware database adapters. // Keeping it separate lets adapters be migrated independently while tenant-aware entry // points fail closed when the configured adapter does not support tenant resolution. type TenantAdapter interface { TenantGetByCode(code string) (*t.Tenant, error) } // TenantRoutedAdapter can return a tenant-specific adapter, e.g. for the // "one tenant, one database" deployment model. type TenantRoutedAdapter interface { TenantAdapterFor(t.TenantID) (TenantBusinessAdapter, TenantAuthAdapter, error) } // TenantAuthAdapter is the tenant-aware authentication and credential capability. // It is separate from Adapter while the remaining persistence domains are migrated. type TenantAuthAdapter interface { TenantAuthGetUniqueRecord(tenantID t.TenantID, unique string) (t.Uid, auth.Level, []byte, time.Time, error) TenantAuthGetRecord(tenantID t.TenantID, user t.Uid, scheme string) (string, auth.Level, []byte, time.Time, error) TenantAuthAddRecord(tenantID t.TenantID, user t.Uid, scheme, unique string, authLvl auth.Level, secret []byte, expires time.Time) error TenantAuthDelScheme(tenantID t.TenantID, user t.Uid, scheme string) error TenantAuthUpdRecord(tenantID t.TenantID, user t.Uid, scheme, unique string, authLvl auth.Level, secret []byte, expires time.Time) error TenantUserGetByCred(tenantID t.TenantID, method, value string) (t.Uid, error) TenantCredUpsert(tenantID t.TenantID, cred *t.Credential) (bool, error) TenantCredGetActive(tenantID t.TenantID, uid t.Uid, method string) (*t.Credential, error) TenantCredGetAll(tenantID t.TenantID, uid t.Uid, method string, validatedOnly bool) ([]t.Credential, error) TenantCredDel(tenantID t.TenantID, uid t.Uid, method, value string) error TenantCredConfirm(tenantID t.TenantID, uid t.Uid, method string) error TenantCredFail(tenantID t.TenantID, uid t.Uid, method string) error } // TenantBusinessAdapter is the mandatory tenant-aware persistence capability // used by request handling code. Adapters which do not implement it fail closed // at the Store boundary instead of falling back to legacy unscoped queries. type TenantBusinessAdapter interface { // TenantBusinessReady reports whether every method below enforces tenant // isolation in its database queries. Startup is rejected while false. TenantBusinessReady() bool TenantUserCreate(t.TenantID, *t.User) error TenantUserGet(t.TenantID, t.Uid) (*t.User, error) TenantUserGetAll(t.TenantID, ...t.Uid) ([]t.User, error) TenantUserDelete(t.TenantID, t.Uid, bool) error TenantUserUpdate(t.TenantID, t.Uid, map[string]any) error TenantUserUpdateTags(t.TenantID, t.Uid, []string, []string, []string) ([]string, error) TenantUserUnreadCount(t.TenantID, ...t.Uid) (map[t.Uid]int, error) TenantUserGetUnvalidated(t.TenantID, time.Time, int) ([]t.Uid, error) TenantTopicCreate(t.TenantID, *t.Topic) error TenantTopicCreateP2P(t.TenantID, *t.Subscription, *t.Subscription) error TenantTopicGet(t.TenantID, string) (*t.Topic, error) TenantTopicsForUser(t.TenantID, t.Uid, bool, *t.QueryOpt) ([]t.Subscription, error) TenantUsersForTopic(t.TenantID, string, bool, *t.QueryOpt) ([]t.Subscription, error) TenantOwnTopics(t.TenantID, t.Uid) ([]string, error) TenantChannelsForUser(t.TenantID, t.Uid) ([]string, error) TenantTopicShare(t.TenantID, string, []*t.Subscription) error TenantTopicDelete(t.TenantID, string, bool, bool) error TenantTopicUpdateOnMessage(t.TenantID, string, *t.Message) error TenantTopicUpdateSubCnt(t.TenantID, string) error TenantTopicUpdate(t.TenantID, string, map[string]any) error TenantTopicOwnerChange(t.TenantID, string, t.Uid) error TenantSubscriptionGet(t.TenantID, string, t.Uid, bool) (*t.Subscription, error) TenantSubsForUser(t.TenantID, t.Uid) ([]t.Subscription, error) TenantSubsForTopic(t.TenantID, string, bool, *t.QueryOpt) ([]t.Subscription, error) TenantSubsUpdate(t.TenantID, string, t.Uid, map[string]any) error TenantSubsDelete(t.TenantID, string, t.Uid) error TenantFind(t.TenantID, string, string, [][]string, []string, bool) ([]t.Subscription, error) TenantFindOne(t.TenantID, string) (string, error) TenantMessageSave(t.TenantID, *t.Message) error TenantMessageGetByClientID(t.TenantID, t.Uid, string) (*t.Message, error) TenantMessageGetOne(t.TenantID, string, int) (*t.Message, error) TenantMessageRecall(t.TenantID, string, int, t.Uid, int, time.Time) (*t.Message, error) TenantMessageEdit(t.TenantID, string, int, t.Uid, int, t.KVMap, any, string, time.Time) (*t.Message, error) TenantMessageGetAll(t.TenantID, string, t.Uid, *t.QueryOpt) ([]t.Message, error) TenantMessageDeleteList(t.TenantID, string, *t.DelMessage) error TenantMessageGetDeleted(t.TenantID, string, t.Uid, *t.QueryOpt) ([]t.DelMessage, error) TenantMessageSearch(t.TenantID, string, t.Uid, string, string, string, int) ([]t.Message, string, error) TenantCallCreate(t.TenantID, *t.CallRecord) error TenantCallAccept(t.TenantID, string, int, time.Time) error TenantCallFinish(t.TenantID, string, int, string, int64, time.Time) error TenantCallList(t.TenantID, t.Uid, string, string, int) ([]t.CallRecord, string, error) TenantWorkbenchModules(t.TenantID, t.Uid) ([]t.WorkbenchModule, int, error) TenantOrganizationDepartments(t.TenantID, t.Uid, string, string, int) ([]t.OrganizationDepartment, string, error) TenantOrganizationMembers(t.TenantID, t.Uid, string, string, int) ([]t.OrganizationMember, string, error) TenantOrganizationSearch(t.TenantID, t.Uid, string, string, string, int) ([]t.OrganizationSearchItem, string, error) TenantMeetingCreate(t.TenantID, t.Uid, *t.Meeting, []t.Uid) (*t.Meeting, error) TenantMeetingGet(t.TenantID, t.Uid, string) (*t.Meeting, error) TenantMeetingUpdate(t.TenantID, t.Uid, string, t.MeetingPatch, string) (*t.Meeting, error) TenantMeetingCancel(t.TenantID, t.Uid, string, string) (*t.Meeting, error) TenantMeetingRemind(t.TenantID, t.Uid, string, string) ([]t.MeetingParticipant, error) TenantMeetingRespond(t.TenantID, t.Uid, string, string, int) (*t.Meeting, error) TenantDailyReportList(t.TenantID, t.Uid, string, string, string, string, string, int) ([]t.DailyReport, string, error) TenantDailyReportCreateDraft(t.TenantID, t.Uid, string, time.Time) (*t.DailyReport, error) TenantDailyReportGet(t.TenantID, t.Uid, string) (*t.DailyReport, error) TenantDailyReportSaveDraft(t.TenantID, t.Uid, string, string, []string, int) (*t.DailyReport, error) TenantDailyReportSubmit(t.TenantID, t.Uid, string, int) (*t.DailyReport, error) TenantDailyReportStats(t.TenantID, t.Uid, time.Time, string) (*t.DailyReportStats, error) TenantDailyReportRemind(t.TenantID, t.Uid, time.Time, []t.Uid, string) ([]t.Uid, error) TenantMonthlyReportList(t.TenantID, t.Uid, string, string, string, string, string, int) ([]t.MonthlyReport, string, error) TenantMonthlyReportCreateDraft(t.TenantID, t.Uid, string, string) (*t.MonthlyReport, error) TenantMonthlyReportGet(t.TenantID, t.Uid, string) (*t.MonthlyReport, error) TenantMonthlyReportSaveDraft(t.TenantID, t.Uid, string, string, string, []string, int) (*t.MonthlyReport, error) TenantMonthlyReportSubmit(t.TenantID, t.Uid, string, int) (*t.MonthlyReport, error) TenantMonthlyReportStats(t.TenantID, t.Uid, string, string) (*t.MonthlyReportStats, error) TenantMailboxConfigGet(t.TenantID, t.Uid) (*t.MailboxConfig, error) TenantMailboxSubmissionCreate(t.TenantID, t.Uid, *t.MailboxSubmission, string) (*t.MailboxSubmission, error) TenantUserProfileGet(t.TenantID, t.Uid) (*t.UserProfile, error) TenantUserProfileUpdate(t.TenantID, t.Uid, t.UserProfilePatch) (*t.UserProfile, error) TenantUserPrivacyGet(t.TenantID, t.Uid) (*t.UserPrivacySettings, error) TenantUserPrivacyUpdate(t.TenantID, t.Uid, t.UserPrivacyPatch) (*t.UserPrivacySettings, error) TenantAppReleaseLatest(t.TenantID, string, string) (*t.AppRelease, error) TenantDevicePushTokenUpsert(t.TenantID, *t.DevicePushToken) (*t.DevicePushToken, error) TenantRiskChallengeCreate(t.TenantID, *t.RiskChallenge) (*t.RiskChallenge, error) TenantRiskChallengeConfirm(t.TenantID, t.Uid, string, string, string, time.Time) (*t.RiskChallenge, error) TenantAbuseReasons(t.TenantID, string) ([]t.AbuseReason, error) TenantAbuseReportCreate(t.TenantID, *t.AbuseReport, string) (*t.AbuseReport, error) TenantFeedbackConfigGet(t.TenantID, t.Uid) (*t.FeedbackConfig, error) TenantFeedbackCreate(t.TenantID, *t.FeedbackSubmission, string) (*t.FeedbackSubmission, error) TenantFavoriteCreate(t.TenantID, *t.FavoriteItem) (*t.FavoriteItem, error) TenantFavoriteList(t.TenantID, t.Uid, string, string, string, int) ([]t.FavoriteItem, string, error) TenantFavoriteGet(t.TenantID, t.Uid, string) (*t.FavoriteItem, error) TenantFavoriteDelete(t.TenantID, t.Uid, string) error TenantDeviceUpsert(t.TenantID, t.Uid, *t.DeviceDef) error TenantDeviceGetAll(t.TenantID, ...t.Uid) (map[t.Uid][]t.DeviceDef, int, error) TenantDeviceDelete(t.TenantID, t.Uid, string) error TenantFileStartUpload(t.TenantID, *t.FileDef) error TenantFileFinishUpload(t.TenantID, *t.FileDef, bool, int64) (*t.FileDef, error) TenantFileGet(t.TenantID, string) (*t.FileDef, error) TenantFileDeleteUnused(t.TenantID, time.Time, int) ([]string, error) TenantFileLinkAttachments(t.TenantID, string, t.Uid, t.Uid, []string) error TenantFileUserHasAccess(t.TenantID, string, t.Uid) (bool, error) TenantPCacheGet(t.TenantID, string) (string, error) TenantPCacheUpsert(t.TenantID, string, string, bool) error TenantPCacheDelete(t.TenantID, string) error TenantPCacheExpire(t.TenantID, string, time.Time) error TenantVerificationCreate(t.TenantID, *t.VerificationCode) error TenantVerificationGet(t.TenantID, string) (*t.VerificationCode, error) TenantVerificationGetActive(t.TenantID, string, string, time.Time) (*t.VerificationCode, error) TenantVerificationFail(t.TenantID, string, t.VerificationState) error TenantVerificationMarkVerified(t.TenantID, string, string, time.Time) error TenantVerificationConsume(t.TenantID, string, string, time.Time) error TenantUserSecurityGetAuthVersion(t.TenantID, t.Uid) (int, error) TenantUserSecurityBumpAuthVersion(t.TenantID, t.Uid, time.Time) (int, error) TenantRealNameGet(t.TenantID, t.Uid) (*t.RealNameVerification, error) TenantRealNameCreate(t.TenantID, *t.RealNameVerification) error TenantActionTokenCreate(t.TenantID, *t.ActionToken) error TenantActionTokenConsume(t.TenantID, t.Uid, string, string, string, time.Time) (*t.ActionToken, error) TenantIndependentPasswordGet(t.TenantID, t.Uid, string) (*t.IndependentPassword, error) TenantIndependentPasswordUpsert(t.TenantID, *t.IndependentPassword) error TenantIndependentPasswordUpdateAttempts(t.TenantID, t.Uid, string, int, *time.Time, time.Time) error TenantYouthModeGet(t.TenantID, t.Uid) (*t.YouthModeSettings, error) TenantYouthModeSet(t.TenantID, t.Uid, bool, int, time.Time) (*t.YouthModeSettings, error) TenantSystemNotificationCreate(t.TenantID, *t.SystemNotification) error TenantSystemNotificationList(t.TenantID, t.Uid, string, int) ([]t.SystemNotification, string, error) TenantSystemNotificationMarkRead(t.TenantID, t.Uid, string, time.Time) error TenantFriendRequestCreate(t.TenantID, *t.FriendRequest) (*t.FriendRequest, error) TenantFriendRequestList(t.TenantID, t.Uid, string, string, string, int) ([]t.FriendRequestListItem, string, error) TenantFriendRequestLatestBetween(t.TenantID, t.Uid, t.Uid) (*t.FriendRequest, error) TenantFriendRequestAccept(t.TenantID, string, t.Uid, time.Time) (*t.FriendRequest, error) TenantFriendRequestResolve(t.TenantID, string, t.Uid, string, time.Time) (*t.FriendRequest, error) TenantFriendshipUpsertPair(t.TenantID, t.Uid, t.Uid, string, time.Time) error TenantFriendshipList(t.TenantID, t.Uid, string, string, string, int) ([]t.FriendListItem, string, error) TenantFriendshipGet(t.TenantID, t.Uid, t.Uid) (*t.Friendship, error) TenantFriendshipUpdateRemark(t.TenantID, t.Uid, t.Uid, string, time.Time) (*t.Friendship, error) TenantFriendshipDeletePair(t.TenantID, t.Uid, t.Uid, time.Time) error TenantUserBlockGet(t.TenantID, t.Uid, t.Uid) (*t.UserBlock, error) TenantUserBlockList(t.TenantID, t.Uid, string, int) ([]t.BlockListItem, string, error) TenantUserBlockUpsert(t.TenantID, t.Uid, t.Uid, time.Time) (*t.UserBlock, error) TenantUserBlockDelete(t.TenantID, t.Uid, t.Uid) error TenantUserSearch(t.TenantID, t.Uid, string, string, string, int) ([]t.User, string, error) }