forked from Ivasoft/mattermost-mobile
[Gekidou] Refactor storage layer (#5471)
* Refactored storage layer - in progress * Refactored DatabaseManager & Operators * Renamed isRecordAppEqualToRaw to isRecordInfoEqualToRaw * Review feedback * Update app/database/models/app/info.ts Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> * Update app/database/models/server/my_team.ts Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> Co-authored-by: Avinash Lingaloo <> Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
import keyMirror from '@utils/key_mirror';
|
||||
|
||||
export const MM_TABLES = {
|
||||
DEFAULT: {
|
||||
APP: 'app',
|
||||
GLOBAL: 'global',
|
||||
SERVERS: 'servers',
|
||||
APP: {
|
||||
INFO: 'Info',
|
||||
GLOBAL: 'Global',
|
||||
SERVERS: 'Servers',
|
||||
},
|
||||
SERVER: {
|
||||
CHANNEL: 'Channel',
|
||||
|
||||
@@ -1,52 +1,37 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ComponentType, useEffect, useState} from 'react';
|
||||
import React, {ComponentType, useState} from 'react';
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Servers from '@database/models/default/servers';
|
||||
import Servers from '@app/database/models/app/servers';
|
||||
|
||||
const {SERVERS} = MM_TABLES.DEFAULT;
|
||||
|
||||
//TODO: Remove when DatabaseManager is Singleton
|
||||
const DBManager = new DatabaseManager();
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
|
||||
export function withServerDatabase<T>(
|
||||
Component: ComponentType<T>,
|
||||
): ComponentType<T> {
|
||||
return function ServerDatabaseComponent(props) {
|
||||
const [database, setDatabase] = useState<Database|unknown>();
|
||||
const db = DatabaseManager.appDatabase?.database;
|
||||
|
||||
// If we don't need to await the async functions this side effect is not needed
|
||||
useEffect(() => {
|
||||
const observer = async (servers: Servers[]) => {
|
||||
const server = servers.reduce((a, b) => (a.lastActiveAt > b.lastActiveAt ? a : b));
|
||||
const observer = async (servers: Servers[]) => {
|
||||
const server = servers.reduce((a, b) => (b.lastActiveAt > a.lastActiveAt ? b : a));
|
||||
|
||||
// The server database should already exists at this point
|
||||
// there should not be a need to await
|
||||
const serverDatabase = await DBManager.retrieveDatabaseInstances([server.url]);
|
||||
if (serverDatabase?.length) {
|
||||
setDatabase(serverDatabase[0]);
|
||||
}
|
||||
};
|
||||
const serverDatabase = DatabaseManager.serverDatabases[server.url].database;
|
||||
if (serverDatabase) {
|
||||
setDatabase(serverDatabase);
|
||||
}
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
// TODO: At this point the database should be already present
|
||||
// there should not be a need to await
|
||||
|
||||
const db = await DBManager.getDefaultDatabase();
|
||||
db?.collections.
|
||||
get(SERVERS).
|
||||
query().
|
||||
observeWithColumns(['last_active_at']).
|
||||
subscribe(observer);
|
||||
};
|
||||
|
||||
init();
|
||||
});
|
||||
db?.collections.
|
||||
get(SERVERS).
|
||||
query().
|
||||
observeWithColumns(['last_active_at']).
|
||||
subscribe(observer);
|
||||
|
||||
if (!database) {
|
||||
return null;
|
||||
|
||||
@@ -4,60 +4,36 @@
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DefaultMigration from '@database/migration/default';
|
||||
import {App, Global, Servers} from '@database/models/default';
|
||||
import {defaultSchema} from '@database/schema/default';
|
||||
import ServerMigration from '@database/migration/server';
|
||||
import {
|
||||
Channel,
|
||||
ChannelInfo,
|
||||
ChannelMembership,
|
||||
CustomEmoji,
|
||||
Draft,
|
||||
File,
|
||||
Group,
|
||||
GroupMembership,
|
||||
GroupsInChannel,
|
||||
GroupsInTeam,
|
||||
MyChannel,
|
||||
MyChannelSettings,
|
||||
MyTeam,
|
||||
Post,
|
||||
PostMetadata,
|
||||
PostsInChannel,
|
||||
PostsInThread,
|
||||
Preference,
|
||||
Reaction,
|
||||
Role,
|
||||
SlashCommand,
|
||||
System,
|
||||
Team,
|
||||
TeamChannelHistory,
|
||||
TeamMembership,
|
||||
TeamSearchHistory,
|
||||
TermsOfService,
|
||||
User,
|
||||
} from '@database/models/server';
|
||||
import {serverSchema} from '@database/schema/server';
|
||||
import logger from '@nozbe/watermelondb/utils/common/logger';
|
||||
import {
|
||||
DatabaseConnectionArgs,
|
||||
DatabaseInstance,
|
||||
DatabaseInstances,
|
||||
DefaultNewServerArgs,
|
||||
GetDatabaseConnectionArgs,
|
||||
Models,
|
||||
RetrievedDatabase,
|
||||
} from '@typings/database/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import IGlobal from '@typings/database/global';
|
||||
import IServers from '@typings/database/servers';
|
||||
import {DeviceEventEmitter, Platform} from 'react-native';
|
||||
import {FileSystem} from 'react-native-unimodules';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
const {SERVERS, GLOBAL} = MM_TABLES.DEFAULT;
|
||||
const DEFAULT_DATABASE = 'default';
|
||||
const RECENTLY_VIEWED_SERVERS = 'RECENTLY_VIEWED_SERVERS';
|
||||
import {MIGRATION_EVENTS, MM_TABLES} from '@constants/database';
|
||||
import AppDataOperator from '@database/operator/app_data_operator';
|
||||
import AppDatabaseMigrations from '@app/database/migration/app';
|
||||
import {Info, Global, Servers} from '@app/database/models/app';
|
||||
import {schema as appSchema} from '@app/database/schema/app';
|
||||
import ServerDatabaseMigrations from '@database/migration/server';
|
||||
import {Channel, ChannelInfo, ChannelMembership, CustomEmoji, Draft, File,
|
||||
Group, GroupMembership, GroupsInChannel, GroupsInTeam, MyChannel, MyChannelSettings, MyTeam,
|
||||
Post, PostMetadata, PostsInChannel, PostsInThread, Preference, Reaction, Role,
|
||||
SlashCommand, System, Team, TeamChannelHistory, TeamMembership, TeamSearchHistory,
|
||||
TermsOfService, User,
|
||||
} from '@database/models/server';
|
||||
import {serverSchema} from '@database/schema/server';
|
||||
import {getActiveServer, getServer} from '@queries/app/servers';
|
||||
import {deleteIOSDatabase} from '@utils/mattermost_managed';
|
||||
import {hashCode} from '@utils/security';
|
||||
|
||||
import type {AppDatabase, CreateServerDatabaseArgs, MigrationEvents, Models, RegisterServerDatabaseArgs, ServerDatabase, ServerDatabases} from '@typings/database/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import type IServers from '@typings/database/models/app/servers';
|
||||
|
||||
import ServerDataOperator from '../../operator/server_data_operator';
|
||||
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
const APP_DATABASE = 'app';
|
||||
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -66,367 +42,283 @@ if (__DEV__) {
|
||||
}
|
||||
|
||||
class DatabaseManager {
|
||||
private defaultDatabase: DatabaseInstance;
|
||||
private readonly defaultModels: Models;
|
||||
private readonly iOSAppGroupDatabase: string | null;
|
||||
private readonly androidFilesDirectory: string | null;
|
||||
private readonly serverModels: Models;
|
||||
public appDatabase?: AppDatabase;
|
||||
public serverDatabases: ServerDatabases = {};
|
||||
private readonly appModels: Models;
|
||||
private readonly databaseDirectory: string | null;
|
||||
private readonly serverModels: Models;
|
||||
|
||||
constructor() {
|
||||
this.defaultModels = [App, Global, Servers];
|
||||
this.serverModels = [
|
||||
Channel,
|
||||
ChannelInfo,
|
||||
ChannelMembership,
|
||||
CustomEmoji,
|
||||
Draft,
|
||||
File,
|
||||
Group,
|
||||
GroupMembership,
|
||||
GroupsInChannel,
|
||||
GroupsInTeam,
|
||||
MyChannel,
|
||||
MyChannelSettings,
|
||||
MyTeam,
|
||||
Post,
|
||||
PostMetadata,
|
||||
PostsInChannel,
|
||||
PostsInThread,
|
||||
Preference,
|
||||
Reaction,
|
||||
Role,
|
||||
SlashCommand,
|
||||
System,
|
||||
Team,
|
||||
TeamChannelHistory,
|
||||
TeamMembership,
|
||||
TeamSearchHistory,
|
||||
TermsOfService,
|
||||
User,
|
||||
];
|
||||
this.iOSAppGroupDatabase = null;
|
||||
this.androidFilesDirectory = null;
|
||||
}
|
||||
constructor() {
|
||||
this.appModels = [Info, Global, Servers];
|
||||
this.serverModels = [
|
||||
Channel,
|
||||
ChannelInfo,
|
||||
ChannelMembership,
|
||||
CustomEmoji,
|
||||
Draft,
|
||||
File,
|
||||
Group,
|
||||
GroupMembership,
|
||||
GroupsInChannel,
|
||||
GroupsInTeam,
|
||||
MyChannel,
|
||||
MyChannelSettings,
|
||||
MyTeam,
|
||||
Post,
|
||||
PostMetadata,
|
||||
PostsInChannel,
|
||||
PostsInThread,
|
||||
Preference,
|
||||
Reaction,
|
||||
Role,
|
||||
SlashCommand,
|
||||
System,
|
||||
Team,
|
||||
TeamChannelHistory,
|
||||
TeamMembership,
|
||||
TeamSearchHistory,
|
||||
TermsOfService,
|
||||
User,
|
||||
];
|
||||
this.databaseDirectory = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* getDatabaseConnection: Given a server url (serverUrl) and a flag (setAsActiveDatabase), this method will attempt
|
||||
* to retrieve an existing database connection previously created for that url. If not found, it will create a new connection and register it in the DEFAULT_DATABASE
|
||||
* @param {GetDatabaseConnectionArgs} getDatabaseConnection
|
||||
* @param {string} getDatabaseConnection.connectionName
|
||||
* @param {string} getDatabaseConnection.serverUrl
|
||||
* @param {boolean} getDatabaseConnection.setAsActiveDatabase
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
*/
|
||||
getDatabaseConnection = async ({
|
||||
serverUrl,
|
||||
setAsActiveDatabase,
|
||||
connectionName,
|
||||
}: GetDatabaseConnectionArgs) => {
|
||||
// We potentially already have this server registered; so we'll try to retrieve it if it is present under DEFAULT_DATABASE/GLOBAL entity
|
||||
const existingServers = (await this.retrieveDatabaseInstances([
|
||||
serverUrl,
|
||||
])) as RetrievedDatabase[];
|
||||
public init = async (serverUrls: string[]): Promise<void> => {
|
||||
await this.createAppDatabase();
|
||||
for await (const serverUrl of serverUrls) {
|
||||
await this.initServerDatabase(serverUrl);
|
||||
}
|
||||
};
|
||||
|
||||
// Since we only passed one serverUrl, we'll expect only one value in the array
|
||||
const serverDatabase = existingServers?.[0];
|
||||
private createAppDatabase = async (): Promise<AppDatabase|undefined> => {
|
||||
try {
|
||||
const modelClasses = this.appModels;
|
||||
const schema = appSchema;
|
||||
|
||||
let connection: DatabaseInstance;
|
||||
const adapter = new LokiJSAdapter({dbName: APP_DATABASE, migrations: AppDatabaseMigrations, schema, useWebWorker: false, useIncrementalIndexedDB: true});
|
||||
|
||||
if (serverDatabase) {
|
||||
// This serverUrl has previously been registered on the app
|
||||
connection = serverDatabase.dbInstance;
|
||||
} else {
|
||||
// Or, it might be that the user has this server on the web-app but not mobile-app; so we'll need to create a new entry for this new serverUrl
|
||||
const databaseName = connectionName ?? urlParse(serverUrl).hostname;
|
||||
connection = await this.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: databaseName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
const database = new Database({adapter, actionsEnabled: true, modelClasses});
|
||||
const operator = new AppDataOperator(database);
|
||||
|
||||
if (setAsActiveDatabase) {
|
||||
await this.setActiveServerDatabase(serverUrl);
|
||||
}
|
||||
this.appDatabase = {
|
||||
database,
|
||||
operator,
|
||||
};
|
||||
|
||||
return connection;
|
||||
};
|
||||
return this.appDatabase;
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
|
||||
/**
|
||||
* createDatabaseConnection: Creates database connection and registers the new connection into the default database. However,
|
||||
* if a database connection could not be created, it will return undefined.
|
||||
* @param {DatabaseConfigs} databaseConnection
|
||||
* @param {boolean} shouldAddToDefaultDatabase
|
||||
*
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
*/
|
||||
createDatabaseConnection = async ({configs, shouldAddToDefaultDatabase = true}: DatabaseConnectionArgs): Promise<DatabaseInstance> => {
|
||||
const {actionsEnabled = true, dbName = DEFAULT_DATABASE, dbType = DatabaseType.DEFAULT, serverUrl = undefined} = configs;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
try {
|
||||
const databaseName = dbType === DatabaseType.DEFAULT ? DEFAULT_DATABASE : dbName;
|
||||
public createServerDatabase = async ({config}: CreateServerDatabaseArgs): Promise<ServerDatabase|undefined> => {
|
||||
const {dbName, displayName, serverUrl} = config;
|
||||
|
||||
// const databaseFilePath = this.getDatabaseFilePath(databaseName);
|
||||
const migrations = dbType === DatabaseType.DEFAULT ? DefaultMigration : ServerMigration;
|
||||
const modelClasses = dbType === DatabaseType.DEFAULT ? this.defaultModels : this.serverModels;
|
||||
const schema = dbType === DatabaseType.DEFAULT ? defaultSchema : serverSchema;
|
||||
if (serverUrl) {
|
||||
try {
|
||||
const databaseFilePath = this.getDatabaseFilePath(dbName);
|
||||
const migrations = ServerDatabaseMigrations;
|
||||
const modelClasses = this.serverModels;
|
||||
const schema = serverSchema;
|
||||
|
||||
const adapter = new LokiJSAdapter({dbName: databaseName, migrations, schema, useWebWorker: false, useIncrementalIndexedDB: true});
|
||||
const adapter = new LokiJSAdapter({dbName, migrations, schema, useWebWorker: false, useIncrementalIndexedDB: true});
|
||||
|
||||
// Registers the new server connection into the DEFAULT database
|
||||
if (serverUrl && shouldAddToDefaultDatabase) {
|
||||
await this.addServerToDefaultDatabase({
|
||||
databaseFilePath: databaseName,
|
||||
displayName: dbName,
|
||||
serverUrl,
|
||||
});
|
||||
}
|
||||
return new Database({adapter, actionsEnabled, modelClasses});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('createDatabaseConnection ERROR:', e);
|
||||
}
|
||||
// Registers the new server connection into the DEFAULT database
|
||||
await this.addServerToAppDatabase({
|
||||
databaseFilePath,
|
||||
displayName: displayName || dbName,
|
||||
serverUrl,
|
||||
});
|
||||
|
||||
return undefined;
|
||||
};
|
||||
const database = new Database({adapter, actionsEnabled: true, modelClasses});
|
||||
const operator = new ServerDataOperator(database);
|
||||
const serverDatabase = {database, operator};
|
||||
|
||||
/**
|
||||
* setActiveServerDatabase: Set the new active server database. The serverUrl is used to ensure that we do not duplicate entries in the default database.
|
||||
* This method should be called when switching to another server.
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
setActiveServerDatabase = async (serverUrl: string) => {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
this.serverDatabases[serverUrl] = serverDatabase;
|
||||
|
||||
if (defaultDatabase) {
|
||||
// retrieve recentlyViewedServers from Global entity
|
||||
const recentlyViewedServers = (await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch()) as IGlobal[];
|
||||
return serverDatabase;
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
}
|
||||
|
||||
if (recentlyViewedServers.length) {
|
||||
// We have previously written something for this flag
|
||||
const flagRecord = recentlyViewedServers[0];
|
||||
const recentList = Array.from(flagRecord.value);
|
||||
recentList.unshift(serverUrl);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// so as to avoid duplicate in this array
|
||||
const sanitizedList = Array.from(new Set(recentList));
|
||||
private initServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
await this.createServerDatabase({
|
||||
config: {
|
||||
dbName: hashCode(serverUrl),
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
await defaultDatabase.action(async () => {
|
||||
await flagRecord.update((record) => {
|
||||
record.value = sanitizedList;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// The flag hasn't been set; so we create the record
|
||||
await defaultDatabase.action(async () => {
|
||||
await defaultDatabase.collections.get(GLOBAL).create((record: IGlobal) => {
|
||||
record.name = RECENTLY_VIEWED_SERVERS;
|
||||
record.value = [serverUrl];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
private addServerToAppDatabase = async ({databaseFilePath, displayName, serverUrl}: RegisterServerDatabaseArgs): Promise<void> => {
|
||||
try {
|
||||
const isServerPresent = await this.isServerPresent(serverUrl); // TODO: Use normalized serverUrl
|
||||
|
||||
/**
|
||||
* isServerPresent : Confirms if the current serverUrl does not already exist in the database
|
||||
* @param {String} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isServerPresent = async (serverUrl: string) => {
|
||||
const allServers = await this.getAllServers([serverUrl]);
|
||||
return allServers?.length > 0;
|
||||
};
|
||||
if (this.appDatabase?.database && !isServerPresent) {
|
||||
const appDatabase = this.appDatabase.database;
|
||||
await appDatabase.action(async () => {
|
||||
const serversCollection = appDatabase.collections.get(SERVERS);
|
||||
await serversCollection.create((server: IServers) => {
|
||||
server.dbPath = databaseFilePath;
|
||||
server.displayName = displayName;
|
||||
server.mentionCount = 0;
|
||||
server.unreadCount = 0;
|
||||
server.url = serverUrl; // TODO: Use normalized serverUrl
|
||||
server.isSecured = urlParse(serverUrl).protocol === 'https';
|
||||
server.lastActiveAt = 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getActiveServerUrl: Use this getter method to retrieve the active server URL.
|
||||
* @returns {string}
|
||||
*/
|
||||
getActiveServerUrl = async (): Promise<string|undefined> => {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
private isServerPresent = async (serverUrl: string): Promise<boolean> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const server = await getServer(this.appDatabase.database, serverUrl);
|
||||
return Boolean(server);
|
||||
}
|
||||
|
||||
if (defaultDatabase) {
|
||||
const serverRecords = await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (serverRecords.length) {
|
||||
const recentServers = serverRecords[0].value as string[];
|
||||
return recentServers[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
public getActiveServerUrl = async (): Promise<string|null|undefined> => {
|
||||
const database = this.appDatabase?.database;
|
||||
if (database) {
|
||||
const server = await getActiveServer(database);
|
||||
return server?.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* getActiveServerDatabase: The DatabaseManager should be the only one setting the active database. Hence, we have made the activeDatabase property private.
|
||||
* Use this getter method to retrieve the active database if it has been set in your code.
|
||||
* @returns {Promise<DatabaseInstance | undefined>}
|
||||
*/
|
||||
getActiveServerDatabase = async (): Promise<DatabaseInstance> => {
|
||||
const serverUrl = await this.getActiveServerUrl();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (serverUrl) {
|
||||
const serverDatabase = await this.getDatabaseConnection({serverUrl, setAsActiveDatabase: false});
|
||||
return serverDatabase;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
public getActiveServerDatabase = async (): Promise<Database|undefined> => {
|
||||
const database = this.appDatabase?.database;
|
||||
if (database) {
|
||||
const server = await getActiveServer(database);
|
||||
if (server?.url) {
|
||||
return this.serverDatabases[server.url].database;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getDefaultDatabase : Returns the default database.
|
||||
* @returns {Database} default database
|
||||
*/
|
||||
getDefaultDatabase = async (): Promise<DatabaseInstance> => {
|
||||
if (!this.defaultDatabase) {
|
||||
await this.setDefaultDatabase();
|
||||
}
|
||||
return this.defaultDatabase;
|
||||
};
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieveDatabaseInstances: Using an array of server URLs, this method creates a database connection for each URL
|
||||
* and return them to the caller.
|
||||
*
|
||||
* @param {string[]} serverUrls
|
||||
* @returns {Promise<RetrievedDatabase[] | null>}
|
||||
*/
|
||||
retrieveDatabaseInstances = async (serverUrls: string[]): Promise<DatabaseInstances[] | null> => {
|
||||
// Retrieve all server records from the default db
|
||||
const allServers = await this.getAllServers(serverUrls);
|
||||
public setActiveServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
await database.action(async () => {
|
||||
const servers = await database.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch();
|
||||
if (servers.length) {
|
||||
servers[0].update((server: Servers) => {
|
||||
server.lastActiveAt = Date.now();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Creates server database instances
|
||||
if (allServers.length) {
|
||||
const databasePromises = allServers.map(async (server: IServers) => {
|
||||
const {displayName, url} = server;
|
||||
public deleteServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
const server = await getServer(database, serverUrl);
|
||||
if (server) {
|
||||
database.action(() => {
|
||||
server.update((record) => {
|
||||
record.lastActiveAt = 0;
|
||||
});
|
||||
});
|
||||
|
||||
// Since we are retrieving existing URL ( and so database connections ) from the 'DEFAULT' database, shouldAddToDefaultDatabase is set to false
|
||||
const dbInstance = await this.createDatabaseConnection({
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: displayName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: url,
|
||||
},
|
||||
shouldAddToDefaultDatabase: false,
|
||||
});
|
||||
return {url, dbInstance, displayName};
|
||||
});
|
||||
const databaseInstances = await Promise.all(databasePromises);
|
||||
return databaseInstances;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
delete this.serverDatabases[serverUrl];
|
||||
this.deleteServerDatabaseFiles(serverUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteDatabase: Removes the *.db file from the App-Group directory for iOS or the files directory on Android.
|
||||
* Also, it removes its entry in the 'servers' table from the DEFAULT database
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
deleteDatabase = async (serverUrl: string): Promise<boolean> => {
|
||||
try {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
let server: IServers;
|
||||
let result = true;
|
||||
if (defaultDatabase) {
|
||||
const serversRecords = (await defaultDatabase.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch()) as IServers[];
|
||||
server = serversRecords?.[0] ?? undefined;
|
||||
public destroyServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
const server = await getServer(database, serverUrl);
|
||||
if (server) {
|
||||
database.action(async () => {
|
||||
await server.destroyPermanently();
|
||||
});
|
||||
|
||||
const globalRecords = await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
const global = globalRecords?.[0] ?? undefined;
|
||||
delete this.serverDatabases[serverUrl];
|
||||
this.deleteServerDatabaseFiles(serverUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (server) {
|
||||
// Perform a delete operation for this server record on the 'servers' table in default database
|
||||
await defaultDatabase.action(async () => {
|
||||
await server.destroyPermanently();
|
||||
});
|
||||
private deleteServerDatabaseFiles = async (serverUrl: string): Promise<void> => {
|
||||
const databaseName = hashCode(serverUrl);
|
||||
|
||||
result = result && true;
|
||||
}
|
||||
if (Platform.OS === 'ios') {
|
||||
// On iOS, we'll delete the *.db file under the shared app-group/databases folder
|
||||
deleteIOSDatabase({databaseName});
|
||||
return;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
// filter out the deleted serverURL
|
||||
const urls = global.value as string[];
|
||||
const filtered = urls.filter((url) => url !== serverUrl);
|
||||
await defaultDatabase.action(async () => {
|
||||
await global.update((record) => {
|
||||
record.value = filtered;
|
||||
});
|
||||
});
|
||||
result = result && true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// On Android, we'll delete both the *.db file and the *.db-journal file
|
||||
const androidFilesDir = `${this.databaseDirectory}databases/`;
|
||||
const databaseFile = `${androidFilesDir}${databaseName}.db`;
|
||||
const databaseJournal = `${androidFilesDir}${databaseName}.db-journal`;
|
||||
|
||||
/**
|
||||
* getAllServers : Retrieves all the servers registered in the default database
|
||||
* @returns {Promise<undefined | Servers[]>}
|
||||
*/
|
||||
private getAllServers = async (serverUrls: string[]) => {
|
||||
// Retrieve all server records from the default db
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
if (defaultDatabase) {
|
||||
const allServers = (await defaultDatabase.collections.
|
||||
get(SERVERS).
|
||||
query(Q.where('url', Q.oneOf(serverUrls))).
|
||||
fetch()) as IServers[];
|
||||
return allServers;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
await FileSystem.deleteAsync(databaseFile);
|
||||
await FileSystem.deleteAsync(databaseJournal);
|
||||
}
|
||||
|
||||
/**
|
||||
* setDefaultDatabase : Sets the default database.
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
*/
|
||||
private setDefaultDatabase = async (): Promise<DatabaseInstance> => {
|
||||
this.defaultDatabase = await this.createDatabaseConnection({
|
||||
configs: {dbName: DEFAULT_DATABASE},
|
||||
shouldAddToDefaultDatabase: false,
|
||||
});
|
||||
factoryReset = async (shouldRemoveDirectory: boolean): Promise<boolean> => {
|
||||
try {
|
||||
//On iOS, we'll delete the databases folder under the shared AppGroup folder
|
||||
if (Platform.OS === 'ios') {
|
||||
deleteIOSDatabase({shouldRemoveDirectory});
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.defaultDatabase;
|
||||
};
|
||||
// On Android, we'll remove the databases folder under the Document Directory
|
||||
const androidFilesDir = `${this.databaseDirectory}databases/`;
|
||||
await FileSystem.deleteAsync(androidFilesDir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addServerToDefaultDatabase: Adds a record into the 'default' database - into the 'servers' table - for this new server connection
|
||||
* @param {string} databaseFilePath
|
||||
* @param {string} displayName
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private addServerToDefaultDatabase = async ({databaseFilePath, displayName, serverUrl}: DefaultNewServerArgs) => {
|
||||
try {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
const isServerPresent = await this.isServerPresent(serverUrl);
|
||||
private buildMigrationCallbacks = (dbName: string) => {
|
||||
const migrationEvents: MigrationEvents = {
|
||||
onSuccess: () => {
|
||||
return DeviceEventEmitter.emit(MIGRATION_EVENTS.MIGRATION_SUCCESS, {
|
||||
dbName,
|
||||
});
|
||||
},
|
||||
onStarted: () => {
|
||||
return DeviceEventEmitter.emit(MIGRATION_EVENTS.MIGRATION_STARTED, {
|
||||
dbName,
|
||||
});
|
||||
},
|
||||
onFailure: (error) => {
|
||||
return DeviceEventEmitter.emit(MIGRATION_EVENTS.MIGRATION_ERROR, {
|
||||
dbName,
|
||||
error,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
if (defaultDatabase && !isServerPresent) {
|
||||
await defaultDatabase.action(async () => {
|
||||
const serversCollection = defaultDatabase.collections.get(SERVERS);
|
||||
await serversCollection.create((server: IServers) => {
|
||||
server.dbPath = databaseFilePath;
|
||||
server.displayName = displayName;
|
||||
server.mentionCount = 0;
|
||||
server.unreadCount = 0;
|
||||
server.url = serverUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('addServerToDefaultDatabase ERROR:', e);
|
||||
}
|
||||
};
|
||||
return migrationEvents;
|
||||
};
|
||||
|
||||
private getDatabaseFilePath = (dbName: string): string => {
|
||||
return Platform.OS === 'ios' ? `${this.databaseDirectory}/${dbName}.db` : `${this.databaseDirectory}${dbName}.db`;
|
||||
};
|
||||
}
|
||||
|
||||
export default DatabaseManager;
|
||||
export default new DatabaseManager();
|
||||
|
||||
@@ -6,187 +6,95 @@ import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
|
||||
import logger from '@nozbe/watermelondb/utils/common/logger';
|
||||
import {DeviceEventEmitter, Platform} from 'react-native';
|
||||
import {FileSystem} from 'react-native-unimodules';
|
||||
|
||||
import {MIGRATION_EVENTS, MM_TABLES} from '@constants/database';
|
||||
import DefaultMigration from '@database/migration/default';
|
||||
import {App, Global, Servers} from '@database/models/default';
|
||||
import {defaultSchema} from '@database/schema/default';
|
||||
import ServerMigration from '@database/migration/server';
|
||||
import {
|
||||
Channel,
|
||||
ChannelInfo,
|
||||
ChannelMembership,
|
||||
CustomEmoji,
|
||||
Draft,
|
||||
File,
|
||||
Group,
|
||||
GroupMembership,
|
||||
GroupsInChannel,
|
||||
GroupsInTeam,
|
||||
MyChannel,
|
||||
MyChannelSettings,
|
||||
MyTeam,
|
||||
Post,
|
||||
PostMetadata,
|
||||
PostsInChannel,
|
||||
PostsInThread,
|
||||
Preference,
|
||||
Reaction,
|
||||
Role,
|
||||
SlashCommand,
|
||||
System,
|
||||
Team,
|
||||
TeamChannelHistory,
|
||||
TeamMembership,
|
||||
TeamSearchHistory,
|
||||
TermsOfService,
|
||||
User,
|
||||
} from '@database/models/server';
|
||||
import {serverSchema} from '@database/schema/server';
|
||||
import {
|
||||
DatabaseConnectionArgs,
|
||||
DatabaseInstance,
|
||||
DatabaseInstances,
|
||||
DefaultNewServerArgs,
|
||||
GetDatabaseConnectionArgs,
|
||||
MigrationEvents,
|
||||
Models,
|
||||
RetrievedDatabase,
|
||||
} from '@typings/database/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import IServers from '@typings/database/servers';
|
||||
import IGlobal from '@typings/database/global';
|
||||
import {deleteIOSDatabase, getIOSAppGroupDetails} from '@utils/mattermost_managed';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
const {SERVERS, GLOBAL} = MM_TABLES.DEFAULT;
|
||||
const DEFAULT_DATABASE = 'default';
|
||||
const RECENTLY_VIEWED_SERVERS = 'RECENTLY_VIEWED_SERVERS';
|
||||
import {MIGRATION_EVENTS, MM_TABLES} from '@constants/database';
|
||||
import AppDataOperator from '@database/operator/app_data_operator';
|
||||
import AppDatabaseMigrations from '@app/database/migration/app';
|
||||
import {Info, Global, Servers} from '@app/database/models/app';
|
||||
import {schema as appSchema} from '@app/database/schema/app';
|
||||
import ServerDatabaseMigrations from '@database/migration/server';
|
||||
import {Channel, ChannelInfo, ChannelMembership, CustomEmoji, Draft, File,
|
||||
Group, GroupMembership, GroupsInChannel, GroupsInTeam, MyChannel, MyChannelSettings, MyTeam,
|
||||
Post, PostMetadata, PostsInChannel, PostsInThread, Preference, Reaction, Role,
|
||||
SlashCommand, System, Team, TeamChannelHistory, TeamMembership, TeamSearchHistory,
|
||||
TermsOfService, User,
|
||||
} from '@database/models/server';
|
||||
import {serverSchema} from '@database/schema/server';
|
||||
import {getActiveServer, getServer} from '@queries/app/servers';
|
||||
import {deleteIOSDatabase, getIOSAppGroupDetails} from '@utils/mattermost_managed';
|
||||
import {hashCode} from '@utils/security';
|
||||
|
||||
import type {AppDatabase, CreateServerDatabaseArgs, RegisterServerDatabaseArgs, MigrationEvents, Models, ServerDatabase, ServerDatabases} from '@typings/database/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import type IServers from '@typings/database/models/app/servers';
|
||||
|
||||
import ServerDataOperator from '../operator/server_data_operator';
|
||||
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
const APP_DATABASE = 'app';
|
||||
|
||||
class DatabaseManager {
|
||||
private defaultDatabase: DatabaseInstance;
|
||||
private readonly defaultModels: Models;
|
||||
private readonly iOSAppGroupDatabase: string | null;
|
||||
private readonly androidFilesDirectory: string | null;
|
||||
public appDatabase?: AppDatabase;
|
||||
public serverDatabases: ServerDatabases = {};
|
||||
private readonly appModels: Models;
|
||||
private readonly databaseDirectory: string | null;
|
||||
private readonly serverModels: Models;
|
||||
|
||||
constructor() {
|
||||
this.defaultModels = [App, Global, Servers];
|
||||
this.appModels = [Info, Global, Servers];
|
||||
this.serverModels = [
|
||||
Channel,
|
||||
ChannelInfo,
|
||||
ChannelMembership,
|
||||
CustomEmoji,
|
||||
Draft,
|
||||
File,
|
||||
Group,
|
||||
GroupMembership,
|
||||
GroupsInChannel,
|
||||
GroupsInTeam,
|
||||
MyChannel,
|
||||
MyChannelSettings,
|
||||
MyTeam,
|
||||
Post,
|
||||
PostMetadata,
|
||||
PostsInChannel,
|
||||
PostsInThread,
|
||||
Preference,
|
||||
Reaction,
|
||||
Role,
|
||||
SlashCommand,
|
||||
System,
|
||||
Team,
|
||||
TeamChannelHistory,
|
||||
TeamMembership,
|
||||
TeamSearchHistory,
|
||||
TermsOfService,
|
||||
User,
|
||||
Channel, ChannelInfo, ChannelMembership, CustomEmoji, Draft, File,
|
||||
Group, GroupMembership, GroupsInChannel, GroupsInTeam, MyChannel, MyChannelSettings, MyTeam,
|
||||
Post, PostMetadata, PostsInChannel, PostsInThread, Preference, Reaction, Role,
|
||||
SlashCommand, System, Team, TeamChannelHistory, TeamMembership, TeamSearchHistory,
|
||||
TermsOfService, User,
|
||||
];
|
||||
|
||||
this.iOSAppGroupDatabase = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupDatabase : null;
|
||||
this.androidFilesDirectory = Platform.OS === 'android' ? FileSystem.documentDirectory : null;
|
||||
this.databaseDirectory = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupDatabase : FileSystem.documentDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDatabaseConnection: Given a server url (serverUrl) and a flag (setAsActiveDatabase), this method will attempt
|
||||
* to retrieve an existing database connection previously created for that url.
|
||||
* If not found, it will create a new connection and register it in the DEFAULT_DATABASE. In this case, it will also
|
||||
* use the provided connectionName as display_name for this server
|
||||
* @param {GetDatabaseConnectionArgs} getDatabaseConnection
|
||||
* @param {string} getDatabaseConnection.connectionName
|
||||
* @param {string} getDatabaseConnection.serverUrl
|
||||
* @param {boolean} getDatabaseConnection.setAsActiveDatabase
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
*/
|
||||
getDatabaseConnection = async ({serverUrl, setAsActiveDatabase, connectionName}: GetDatabaseConnectionArgs) => {
|
||||
// We potentially already have this server registered; so we'll try to retrieve it if it is present under DEFAULT_DATABASE/GLOBAL entity
|
||||
const existingServers = await this.retrieveDatabaseInstances([serverUrl]) as RetrievedDatabase[];
|
||||
|
||||
// Since we only passed one serverUrl, we'll expect only one value in the array
|
||||
const serverDatabase = existingServers?.[0];
|
||||
|
||||
let connection: DatabaseInstance;
|
||||
|
||||
if (serverDatabase) {
|
||||
// This serverUrl has previously been registered on the app
|
||||
connection = serverDatabase.dbInstance;
|
||||
} else {
|
||||
// Or, it might be that the user has this server on the web-app but not mobile-app; so we'll need to create a new entry for this new serverUrl
|
||||
const databaseName = connectionName ?? urlParse(serverUrl).hostname;
|
||||
connection = await this.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: databaseName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
* init : Retrieves all the servers registered in the default database
|
||||
* @param {string[]} serverUrls
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public init = async (serverUrls: string[]): Promise<void> => {
|
||||
await this.createAppDatabase();
|
||||
for await (const serverUrl of serverUrls) {
|
||||
await this.initServerDatabase(serverUrl);
|
||||
}
|
||||
|
||||
if (setAsActiveDatabase) {
|
||||
await this.setActiveServerDatabase(serverUrl);
|
||||
}
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
/**
|
||||
* createDatabaseConnection: Creates database connection and registers the new connection into the default database. However,
|
||||
* if a database connection could not be created, it will return undefined.
|
||||
* @param {MMDatabaseConnection} databaseConnection
|
||||
* @param {boolean} shouldAddToDefaultDatabase
|
||||
*
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
* createAppDatabase: Creates the App database. However,
|
||||
* if a database could not be created, it will return undefined.
|
||||
* @returns {Promise<AppDatabase|undefined>}
|
||||
*/
|
||||
createDatabaseConnection = async ({configs, shouldAddToDefaultDatabase = true}: DatabaseConnectionArgs): Promise<DatabaseInstance> => {
|
||||
const {actionsEnabled = true, dbName = DEFAULT_DATABASE, dbType = DatabaseType.DEFAULT, serverUrl = undefined} = configs;
|
||||
|
||||
private createAppDatabase = async (): Promise<AppDatabase|undefined> => {
|
||||
try {
|
||||
const databaseName = dbType === DatabaseType.DEFAULT ? DEFAULT_DATABASE : dbName;
|
||||
const databaseName = APP_DATABASE;
|
||||
|
||||
const databaseFilePath = this.getDatabaseFilePath(databaseName);
|
||||
const migrations = dbType === DatabaseType.DEFAULT ? DefaultMigration : ServerMigration;
|
||||
const modelClasses = dbType === DatabaseType.DEFAULT ? this.defaultModels : this.serverModels;
|
||||
const schema = dbType === DatabaseType.DEFAULT ? defaultSchema : serverSchema;
|
||||
const modelClasses = this.appModels;
|
||||
const schema = appSchema;
|
||||
|
||||
const adapter = new SQLiteAdapter({
|
||||
dbName: databaseFilePath,
|
||||
migrationEvents: this.buildMigrationCallbacks(databaseName),
|
||||
migrations,
|
||||
migrations: AppDatabaseMigrations,
|
||||
schema,
|
||||
});
|
||||
|
||||
// Registers the new server connection into the DEFAULT database
|
||||
if (serverUrl && shouldAddToDefaultDatabase) {
|
||||
await this.addServerToDefaultDatabase({
|
||||
databaseFilePath,
|
||||
displayName: dbName,
|
||||
serverUrl,
|
||||
});
|
||||
}
|
||||
const database = new Database({adapter, actionsEnabled: true, modelClasses});
|
||||
const operator = new AppDataOperator(database);
|
||||
|
||||
return new Database({adapter, actionsEnabled, modelClasses});
|
||||
this.appDatabase = {
|
||||
database,
|
||||
operator,
|
||||
};
|
||||
|
||||
return this.appDatabase;
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
@@ -195,186 +103,228 @@ class DatabaseManager {
|
||||
};
|
||||
|
||||
/**
|
||||
* setActiveServerDatabase: Set the new active server database. The serverUrl is used to ensure that we do not duplicate entries in the default database.
|
||||
* This method should be called when switching to another server.
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
* createServerDatabase: Creates a server database and registers the the server in the app database. However,
|
||||
* if a database connection could not be created, it will return undefined.
|
||||
* @param {CreateServerDatabaseArgs} createServerDatabaseArgs
|
||||
*
|
||||
* @returns {Promise<ServerDatabase|undefined>}
|
||||
*/
|
||||
setActiveServerDatabase = async (serverUrl: string) => {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
if (defaultDatabase) {
|
||||
// retrieve recentlyViewedServers from Global entity
|
||||
const recentlyViewedServers = await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
public createServerDatabase = async ({config}: CreateServerDatabaseArgs): Promise<ServerDatabase|undefined> => {
|
||||
const {dbName, displayName, serverUrl} = config;
|
||||
|
||||
if (recentlyViewedServers.length) {
|
||||
// We have previously written something for this flag
|
||||
const flagRecord = recentlyViewedServers[0];
|
||||
const recentList = Array.from(flagRecord.value);
|
||||
recentList.unshift(serverUrl);
|
||||
if (serverUrl) {
|
||||
try {
|
||||
const databaseName = hashCode(dbName);
|
||||
const databaseFilePath = this.getDatabaseFilePath(databaseName);
|
||||
const migrations = ServerDatabaseMigrations;
|
||||
const modelClasses = this.serverModels;
|
||||
const schema = serverSchema;
|
||||
|
||||
// so as to avoid duplicate in this array
|
||||
const sanitizedList = Array.from(new Set(recentList));
|
||||
const adapter = new SQLiteAdapter({
|
||||
dbName: databaseFilePath,
|
||||
migrationEvents: this.buildMigrationCallbacks(databaseName),
|
||||
migrations,
|
||||
schema,
|
||||
});
|
||||
|
||||
await defaultDatabase.action(async () => {
|
||||
await flagRecord.update((record) => {
|
||||
record.value = sanitizedList;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// The flag hasn't been set; so we create the record
|
||||
await defaultDatabase.action(async () => {
|
||||
await defaultDatabase.collections.get(GLOBAL).create((record: IGlobal) => {
|
||||
record.name = RECENTLY_VIEWED_SERVERS;
|
||||
record.value = [serverUrl];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
// Registers the new server connection into the DEFAULT database
|
||||
await this.addServerToAppDatabase({
|
||||
databaseFilePath,
|
||||
displayName: displayName || dbName,
|
||||
serverUrl,
|
||||
});
|
||||
|
||||
/**
|
||||
* getActiveServerUrl: Use this getter method to retrieve the active server URL.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
getActiveServerUrl = async (): Promise<string | undefined> => {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
const database = new Database({adapter, actionsEnabled: true, modelClasses});
|
||||
const operator = new ServerDataOperator(database);
|
||||
const serverDatabase = {database, operator};
|
||||
|
||||
if (defaultDatabase) {
|
||||
const serverRecords = await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
this.serverDatabases[serverUrl] = serverDatabase;
|
||||
|
||||
if (serverRecords.length) {
|
||||
const recentServers = serverRecords[0].value as string[];
|
||||
return recentServers[0];
|
||||
return serverDatabase;
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* getActiveServerDatabase: The DatabaseManager should be the only one setting the active database. Hence, we have made the activeDatabase property private.
|
||||
* Use this getter method to retrieve the active database if it has been set in your code.
|
||||
* @returns {Promise<MostRecentConnection | undefined>}
|
||||
*/
|
||||
getActiveServerDatabase = async (): Promise<DatabaseInstance> => {
|
||||
const serverUrl = await this.getActiveServerUrl();
|
||||
|
||||
if (serverUrl) {
|
||||
const serverDatabase = await this.getDatabaseConnection({serverUrl, setAsActiveDatabase: false});
|
||||
return serverDatabase;
|
||||
}
|
||||
return undefined;
|
||||
* initServerDatabase : initializes the server database.
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private initServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
await this.createServerDatabase({
|
||||
config: {
|
||||
dbName: serverUrl,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* getDefaultDatabase : Returns the default database.
|
||||
* @returns {Database} default database
|
||||
*/
|
||||
getDefaultDatabase = async (): Promise<DatabaseInstance> => {
|
||||
if (!this.defaultDatabase) {
|
||||
await this.setDefaultDatabase();
|
||||
}
|
||||
return this.defaultDatabase;
|
||||
};
|
||||
* addServerToAppDatabase: Adds a record in the 'app' database - into the 'servers' table - for this new server connection
|
||||
* @param {string} databaseFilePath
|
||||
* @param {string} displayName
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private addServerToAppDatabase = async ({databaseFilePath, displayName, serverUrl}: RegisterServerDatabaseArgs): Promise<void> => {
|
||||
try {
|
||||
const isServerPresent = await this.isServerPresent(serverUrl); // TODO: Use normalized serverUrl
|
||||
|
||||
/**
|
||||
* retrieveDatabaseInstances: Using an array of server URLs, this method creates a database connection for each URL
|
||||
* and return them to the caller.
|
||||
*
|
||||
* @param {string[]} serverUrls
|
||||
* @returns {Promise<RetrievedDatabase[] | null>}
|
||||
*/
|
||||
retrieveDatabaseInstances = async (serverUrls: string[]): Promise<DatabaseInstances[] | null> => {
|
||||
// Retrieve all server records from the default db
|
||||
const allServers = await this.getAllServers(serverUrls);
|
||||
|
||||
// Creates server database instances
|
||||
if (allServers.length) {
|
||||
const databasePromises = await allServers.map(async (server: IServers) => {
|
||||
const {displayName, url} = server;
|
||||
|
||||
// Since we are retrieving existing URL ( and so database connections ) from the 'DEFAULT' database, shouldAddToDefaultDatabase is set to false
|
||||
const dbInstance = await this.createDatabaseConnection({
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: displayName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: url,
|
||||
},
|
||||
shouldAddToDefaultDatabase: false,
|
||||
if (this.appDatabase?.database && !isServerPresent) {
|
||||
const appDatabase = this.appDatabase.database;
|
||||
await appDatabase.action(async () => {
|
||||
const serversCollection = appDatabase.collections.get(SERVERS);
|
||||
await serversCollection.create((server: IServers) => {
|
||||
server.dbPath = databaseFilePath;
|
||||
server.displayName = displayName;
|
||||
server.mentionCount = 0;
|
||||
server.unreadCount = 0;
|
||||
server.url = serverUrl; // TODO: Use normalized serverUrl
|
||||
server.isSecured = urlParse(serverUrl).protocol === 'https';
|
||||
server.lastActiveAt = 0;
|
||||
});
|
||||
});
|
||||
|
||||
return {dbInstance, displayName, url};
|
||||
});
|
||||
const databaseInstances = await Promise.all(databasePromises);
|
||||
return databaseInstances;
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* deleteDatabase: Removes the *.db file from the App-Group directory for iOS or the files directory on Android.
|
||||
* Also, it removes its entry in the 'servers' table from the DEFAULT database
|
||||
* isServerPresent : Confirms if the current serverUrl does not already exist in the database
|
||||
* @param {String} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
private isServerPresent = async (serverUrl: string): Promise<boolean> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const server = await getServer(this.appDatabase.database, serverUrl);
|
||||
return Boolean(server);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getActiveServerUrl: Get the record for active server database.
|
||||
* @returns {Promise<string|null|undefined>}
|
||||
*/
|
||||
public getActiveServerUrl = async (): Promise<string|null|undefined> => {
|
||||
const database = this.appDatabase?.database;
|
||||
if (database) {
|
||||
const server = await getActiveServer(database);
|
||||
return server?.url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* getActiveServerDatabase: Get the record for active server database.
|
||||
* @returns {Promise<Database|undefined>}
|
||||
*/
|
||||
public getActiveServerDatabase = async (): Promise<Database|undefined> => {
|
||||
const database = this.appDatabase?.database;
|
||||
if (database) {
|
||||
const server = await getActiveServer(database);
|
||||
if (server?.url) {
|
||||
return this.serverDatabases[server.url].database;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* setActiveServerDatabase: Set the new active server database.
|
||||
* This method should be called when switching to another server.
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public setActiveServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
await database.action(async () => {
|
||||
const servers = await database.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch();
|
||||
if (servers.length) {
|
||||
servers[0].update((server: Servers) => {
|
||||
server.lastActiveAt = Date.now();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* deleteServerDatabase: Removes the *.db file from the App-Group directory for iOS or the files directory on Android.
|
||||
* Also, it sets the last_active_at to '0' entry in the 'servers' table from the APP database
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
deleteDatabase = async (serverUrl: string): Promise<boolean> => {
|
||||
try {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
let server: IServers;
|
||||
let result = true;
|
||||
|
||||
if (defaultDatabase) {
|
||||
// NOTE: We are deleting this 'database' entry in the SERVERS entity on the DEFAULT database; for that we retrieve its record first.
|
||||
const serversRecords = (await defaultDatabase.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch()) as IServers[];
|
||||
server = serversRecords?.[0] ?? undefined;
|
||||
|
||||
const globalRecords = await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
const global = globalRecords?.[0] ?? undefined;
|
||||
|
||||
if (server) {
|
||||
const databaseName = server.displayName;
|
||||
|
||||
// Perform a delete operation for this server record on the 'servers' table in default database
|
||||
await defaultDatabase.action(async () => {
|
||||
await server.destroyPermanently();
|
||||
public deleteServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
const server = await getServer(database, serverUrl);
|
||||
if (server) {
|
||||
database.action(() => {
|
||||
server.update((record) => {
|
||||
record.lastActiveAt = 0;
|
||||
});
|
||||
});
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
// On iOS, we'll delete the *.db file under the shared app-group/databases folder
|
||||
deleteIOSDatabase({databaseName});
|
||||
return true;
|
||||
}
|
||||
|
||||
// On Android, we'll delete both the *.db file and the *.db-journal file
|
||||
const androidFilesDir = `${this.androidFilesDirectory}databases/`;
|
||||
const databaseFile = `${androidFilesDir}${databaseName}.db`;
|
||||
const databaseJournal = `${androidFilesDir}${databaseName}.db-journal`;
|
||||
|
||||
await FileSystem.deleteAsync(databaseFile);
|
||||
await FileSystem.deleteAsync(databaseJournal);
|
||||
|
||||
result = result && true;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
// filter out the deleted serverURL
|
||||
const urls = global.value as string[];
|
||||
const filtered = urls.filter((url) => url !== serverUrl);
|
||||
await defaultDatabase.action(async () => {
|
||||
await global.update((record) => {
|
||||
record.value = filtered;
|
||||
});
|
||||
});
|
||||
result = result && true;
|
||||
}
|
||||
return result;
|
||||
delete this.serverDatabases[serverUrl];
|
||||
this.deleteServerDatabaseFiles(serverUrl);
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* destroyServerDatabase: Removes the *.db file from the App-Group directory for iOS or the files directory on Android.
|
||||
* Also, removes the entry in the 'servers' table from the APP database
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public destroyServerDatabase = async (serverUrl: string): Promise<void> => {
|
||||
if (this.appDatabase?.database) {
|
||||
const database = this.appDatabase?.database;
|
||||
const server = await getServer(database, serverUrl);
|
||||
if (server) {
|
||||
database.action(async () => {
|
||||
await server.destroyPermanently();
|
||||
});
|
||||
|
||||
delete this.serverDatabases[serverUrl];
|
||||
this.deleteServerDatabaseFiles(serverUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteServerDatabaseFiles: Removes the *.db file from the App-Group directory for iOS or the files directory on Android.
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private deleteServerDatabaseFiles = async (serverUrl: string): Promise<void> => {
|
||||
const databaseName = hashCode(serverUrl);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
// On iOS, we'll delete the *.db file under the shared app-group/databases folder
|
||||
deleteIOSDatabase({databaseName});
|
||||
return;
|
||||
}
|
||||
|
||||
// On Android, we'll delete both the *.db file and the *.db-journal file
|
||||
const androidFilesDir = `${this.databaseDirectory}databases/`;
|
||||
const databaseFile = `${androidFilesDir}${databaseName}.db`;
|
||||
const databaseJournal = `${androidFilesDir}${databaseName}.db-journal`;
|
||||
|
||||
await FileSystem.deleteAsync(databaseFile);
|
||||
await FileSystem.deleteAsync(databaseJournal);
|
||||
}
|
||||
|
||||
/**
|
||||
* factoryReset: Removes the databases directory and all its contents on the respective platform
|
||||
@@ -390,7 +340,7 @@ class DatabaseManager {
|
||||
}
|
||||
|
||||
// On Android, we'll remove the databases folder under the Document Directory
|
||||
const androidFilesDir = `${FileSystem.documentDirectory}databases/`;
|
||||
const androidFilesDir = `${this.databaseDirectory}databases/`;
|
||||
await FileSystem.deleteAsync(androidFilesDir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -398,74 +348,6 @@ class DatabaseManager {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* isServerPresent : Confirms if the current serverUrl does not already exist in the database
|
||||
* @param {String} serverUrl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
private isServerPresent = async (serverUrl: string) => {
|
||||
const allServers = await this.getAllServers([serverUrl]);
|
||||
return allServers?.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* getAllServers : Retrieves all the servers registered in the default database
|
||||
* @returns {Promise<Servers[]>}
|
||||
*/
|
||||
private getAllServers = async (serverUrls: string[]) => {
|
||||
// Retrieve all server records from the default db
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
|
||||
if (defaultDatabase) {
|
||||
const allServers = (await defaultDatabase.collections.get(SERVERS).query(Q.where('url', Q.oneOf(serverUrls))).fetch() as IServers[]);
|
||||
return allServers;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* setDefaultDatabase : Sets the default database.
|
||||
* @returns {Promise<DatabaseInstance>}
|
||||
*/
|
||||
private setDefaultDatabase = async (): Promise<DatabaseInstance> => {
|
||||
this.defaultDatabase = await this.createDatabaseConnection({
|
||||
configs: {dbName: DEFAULT_DATABASE},
|
||||
shouldAddToDefaultDatabase: false,
|
||||
});
|
||||
|
||||
return this.defaultDatabase;
|
||||
};
|
||||
|
||||
/**
|
||||
* addServerToDefaultDatabase: Adds a record into the 'default' database - into the 'servers' table - for this new server connection
|
||||
* @param {string} databaseFilePath
|
||||
* @param {string} displayName
|
||||
* @param {string} serverUrl
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private addServerToDefaultDatabase = async ({databaseFilePath, displayName, serverUrl}: DefaultNewServerArgs) => {
|
||||
try {
|
||||
const defaultDatabase = await this.getDefaultDatabase();
|
||||
const isServerPresent = await this.isServerPresent(serverUrl);
|
||||
|
||||
if (defaultDatabase && !isServerPresent) {
|
||||
await defaultDatabase.action(async () => {
|
||||
const serversCollection = defaultDatabase.collections.get(SERVERS);
|
||||
await serversCollection.create((server: IServers) => {
|
||||
server.dbPath = databaseFilePath;
|
||||
server.displayName = displayName;
|
||||
server.mentionCount = 0;
|
||||
server.unreadCount = 0;
|
||||
server.url = serverUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO : report to sentry? Show something on the UI ?
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* buildMigrationCallbacks: Creates a set of callbacks that can be used to monitor the migration process.
|
||||
* For example, we can display a processing spinner while we have a migration going on. Moreover, we can also
|
||||
@@ -507,7 +389,7 @@ class DatabaseManager {
|
||||
* @returns {string}
|
||||
*/
|
||||
private getDatabaseFilePath = (dbName: string): string => {
|
||||
return Platform.OS === 'ios' ? `${this.iOSAppGroupDatabase}/${dbName}.db` : `${FileSystem.documentDirectory}${dbName}.db`;
|
||||
return Platform.OS === 'ios' ? `${this.databaseDirectory}/${dbName}.db` : `${this.databaseDirectory}${dbName}.db`;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -517,4 +399,4 @@ if (!__DEV__) {
|
||||
logger.silence();
|
||||
}
|
||||
|
||||
export default DatabaseManager;
|
||||
export default new DatabaseManager();
|
||||
|
||||
@@ -4,233 +4,84 @@
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import IGlobal from '@typings/database/global';
|
||||
import IServers from '@typings/database/servers';
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
import type IServers from '@typings/database/models/app/servers';
|
||||
|
||||
const {GLOBAL, SERVERS} = MM_TABLES.DEFAULT;
|
||||
const RECENTLY_VIEWED_SERVERS = 'RECENTLY_VIEWED_SERVERS';
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
|
||||
// NOTE : On the mock Database Manager, we cannot test for :
|
||||
// 1. Android/iOS file path
|
||||
// 2. Deletion of the 'databases' folder on those two platforms
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** Database Manager tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager | null;
|
||||
|
||||
beforeEach(() => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const serverUrls = ['https://appv1.mattermost.com', 'https://appv2.mattermost.com'];
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(serverUrls);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
databaseManagerClient = null;
|
||||
});
|
||||
|
||||
const createTwoConnections = async () => {
|
||||
await databaseManagerClient!.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'connection1',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'https://appv1.mattermost.com',
|
||||
},
|
||||
});
|
||||
await databaseManagerClient!.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'connection2',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'https://appv2.mattermost.com',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('=> should return a default database', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnAddServerToDefaultDatabase = jest.spyOn(databaseManagerClient as any, 'addServerToDefaultDatabase');
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
|
||||
const defaultDB = await databaseManagerClient!.getDefaultDatabase();
|
||||
|
||||
expect(defaultDB).toBeInstanceOf(Database);
|
||||
expect(spyOnAddServerToDefaultDatabase).not.toHaveBeenCalledTimes(1);
|
||||
expect(appDatabase).toBeInstanceOf(Database);
|
||||
expect(Object.keys(DatabaseManager.serverDatabases).length).toBe(2);
|
||||
});
|
||||
|
||||
it('=> should create a new server connection', async () => {
|
||||
expect.assertions(2);
|
||||
it('=> should create a new server database', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const spyOnAddServerToDefaultDatabase = jest.spyOn(databaseManagerClient as any, 'addServerToDefaultDatabase');
|
||||
const spyOnAddServerToDefaultDatabase = jest.spyOn(DatabaseManager as any, 'addServerToAppDatabase');
|
||||
|
||||
const connection1 = await databaseManagerClient!.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
const connection1 = await DatabaseManager!.createServerDatabase({
|
||||
config: {
|
||||
dbName: 'community mattermost',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'https://appv1.mattermost.com',
|
||||
},
|
||||
});
|
||||
|
||||
expect(connection1).toBeInstanceOf(Database);
|
||||
expect(connection1?.database).toBeInstanceOf(Database);
|
||||
expect(connection1?.operator).toBeInstanceOf(ServerDataOperator);
|
||||
expect(spyOnAddServerToDefaultDatabase).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('=> should switch between active server connections', async () => {
|
||||
expect.assertions(6);
|
||||
let adapter;
|
||||
it('=> should switch between active servers', async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
const activeServerA = await databaseManagerClient!.getActiveServerDatabase();
|
||||
let activeServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
const serverA = await DatabaseManager.getActiveServerDatabase();
|
||||
|
||||
// as we haven't set an active server yet, we should be getting undefined in the activeServer variable
|
||||
expect(activeServerA).toBeUndefined();
|
||||
// as we haven't set an active server yet, so the first registered server should be the active one
|
||||
expect(activeServerUrl).toBe(serverUrls[0]);
|
||||
expect(serverA).toEqual(DatabaseManager.serverDatabases[serverUrls[0]].database);
|
||||
|
||||
const setActiveServer = async (serverUrl: string) => {
|
||||
// now we set the active database
|
||||
await databaseManagerClient!.setActiveServerDatabase(serverUrl);
|
||||
};
|
||||
await DatabaseManager.setActiveServerDatabase('https://appv2.mattermost.com');
|
||||
|
||||
await setActiveServer('https://appv1.mattermost.com');
|
||||
|
||||
// let's verify if we now have a value for activeServer
|
||||
const activeServerB = await databaseManagerClient!.getActiveServerDatabase();
|
||||
expect(activeServerB).toBeDefined();
|
||||
|
||||
adapter = activeServerB!.adapter as any;
|
||||
const currentDBName = adapter.underlyingAdapter._dbName;
|
||||
expect(currentDBName).toStrictEqual('appv1.mattermost.com');
|
||||
|
||||
// spice things up; we'll set a new server and verify if the value of activeServer changes
|
||||
await setActiveServer('https://appv2.mattermost.com');
|
||||
const activeServerC = await databaseManagerClient!.getActiveServerDatabase();
|
||||
expect(activeServerC).toBeDefined();
|
||||
|
||||
adapter = activeServerC!.adapter as any;
|
||||
const newDBName = adapter.underlyingAdapter._dbName;
|
||||
expect(newDBName).toStrictEqual('appv2.mattermost.com');
|
||||
|
||||
const defaultDatabase = await databaseManagerClient!.getDefaultDatabase();
|
||||
const records = await defaultDatabase!.collections.get(MM_TABLES.DEFAULT.GLOBAL).query(Q.where('name', 'RECENTLY_VIEWED_SERVERS')).fetch() as IGlobal[];
|
||||
const recentlyViewedServers = records?.[0]?.value;
|
||||
expect(recentlyViewedServers?.length).toBe(2);
|
||||
// new active server should change and we have a Database and is active
|
||||
activeServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
const serverB = await DatabaseManager.getActiveServerDatabase();
|
||||
expect(activeServerUrl).toBe(serverUrls[1]);
|
||||
expect(serverB).toEqual(DatabaseManager.serverDatabases[serverUrls[1]].database);
|
||||
});
|
||||
|
||||
it('=> should retrieve all database instances matching serverUrls parameter', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
await createTwoConnections();
|
||||
|
||||
const spyOnCreateDatabaseConnection = jest.spyOn(databaseManagerClient!, 'createDatabaseConnection');
|
||||
|
||||
const dbInstances = await databaseManagerClient!.retrieveDatabaseInstances([
|
||||
'https://xunity2.mattermost.com',
|
||||
'https://appv2.mattermost.com',
|
||||
'https://appv1.mattermost.com',
|
||||
]);
|
||||
|
||||
expect(dbInstances).toBeTruthy();
|
||||
const numDbInstances = dbInstances?.length ?? 0;
|
||||
|
||||
// The Database Manager will call the 'createDatabaseConnection' method in consequence of the number of database connection present in dbInstances array
|
||||
expect(spyOnCreateDatabaseConnection).toHaveBeenCalledTimes(numDbInstances);
|
||||
|
||||
// We should have two active database connection
|
||||
expect(numDbInstances).toEqual(2);
|
||||
});
|
||||
|
||||
it('=> should retrieve existing database instances matching serverUrl parameter', async () => {
|
||||
it('=> should delete appv1 server from the servers table of App database', async () => {
|
||||
expect.assertions(2);
|
||||
await createTwoConnections();
|
||||
const spyOnRetrieveDatabaseInstances = jest.spyOn(databaseManagerClient!, 'retrieveDatabaseInstances');
|
||||
const connection = await databaseManagerClient!.getDatabaseConnection({serverUrl: 'https://appv1.mattermost.com', setAsActiveDatabase: false});
|
||||
expect(spyOnRetrieveDatabaseInstances).toHaveBeenCalledTimes(1);
|
||||
expect(connection).toBeDefined();
|
||||
});
|
||||
|
||||
//todo: test the current active database together with the getDatabaseConnection method
|
||||
await DatabaseManager.setActiveServerDatabase('https://appv1.mattermost.com');
|
||||
await DatabaseManager.destroyServerDatabase('https://appv1.mattermost.com');
|
||||
|
||||
it('=> should have records of Servers set in the servers table of the default database', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const defaultDB = await databaseManagerClient!.getDefaultDatabase();
|
||||
expect(defaultDB).toBeDefined();
|
||||
await createTwoConnections();
|
||||
const serversRecords = await defaultDB!.collections.get(SERVERS).query().fetch() as IServers[];
|
||||
expect(serversRecords).toBeDefined();
|
||||
|
||||
// We have call the 'DatabaseManager.setActiveServerDatabase' twice in the previous test case; that implies that we have 2 records in the 'servers' table
|
||||
expect(serversRecords.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('=> should delete appv1 server from the servers table of Default database', async () => {
|
||||
expect.assertions(3);
|
||||
await createTwoConnections();
|
||||
|
||||
const defaultDatabase = await databaseManagerClient!.getDefaultDatabase();
|
||||
|
||||
await databaseManagerClient?.setActiveServerDatabase('https://appv1.mattermost.com');
|
||||
await databaseManagerClient?.setActiveServerDatabase('https://appv2.mattermost.com');
|
||||
|
||||
const fetchGlobalRecords = async () => {
|
||||
const initialGlobalRecords = await defaultDatabase!.collections.get(GLOBAL).query(Q.where('name', RECENTLY_VIEWED_SERVERS)).fetch() as IGlobal[];
|
||||
return initialGlobalRecords?.[0].value as string[];
|
||||
const fetchServerRecords = async (serverUrl: string) => {
|
||||
const servers = await DatabaseManager.appDatabase?.database!.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch() as IServers[];
|
||||
return servers.length;
|
||||
};
|
||||
|
||||
const recentServers = await fetchGlobalRecords();
|
||||
expect(recentServers.length).toBe(2);
|
||||
const destroyed = await fetchServerRecords(serverUrls[0]);
|
||||
expect(destroyed).toBe(0);
|
||||
|
||||
// Removing database for appv1 connection
|
||||
const isAppV1Removed = await databaseManagerClient!.deleteDatabase('https://appv1.mattermost.com');
|
||||
expect(isAppV1Removed).toBe(true);
|
||||
|
||||
// Verifying in the database to confirm if its record was deleted
|
||||
|
||||
const updatedRecentServers = await fetchGlobalRecords();
|
||||
expect(updatedRecentServers.length).toBe(1);
|
||||
});
|
||||
|
||||
it('=> should enforce uniqueness of connections using serverUrl as key', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
// We can't have more than one connection with the same server url
|
||||
const serverUrl = 'https://appv3.mattermost.com';
|
||||
await databaseManagerClient!.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'community mattermost',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
|
||||
await databaseManagerClient!.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'duplicate server',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultDB = await databaseManagerClient!.getDefaultDatabase();
|
||||
|
||||
const allServers = defaultDB && await defaultDB.collections.get(SERVERS).query().fetch() as IServers[];
|
||||
|
||||
// We should be having some servers returned here
|
||||
expect(allServers?.length).toBeGreaterThan(0);
|
||||
|
||||
const occurrences = allServers?.map((server) => server.url).reduce((acc, cur) => (cur === serverUrl ? acc + 1 : acc), 0);
|
||||
|
||||
// We should only have one occurrence of the 'https://appv3.mattermost.com' url
|
||||
expect(occurrences).toEqual(1);
|
||||
const activeServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
expect(activeServerUrl).toEqual(serverUrls[1]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import {getIOSAppGroupDetails} from '@utils/mattermost_managed';
|
||||
|
||||
import DatabaseManager from './index';
|
||||
|
||||
import type IServers from '@typings/database/models/app/servers';
|
||||
|
||||
export default async () => {
|
||||
const databaseClient = new DatabaseManager();
|
||||
await DatabaseManager.init([]);
|
||||
|
||||
// Test: It should return the iOS App-Group shared directory
|
||||
const testAppGroupDirectory = () => {
|
||||
@@ -18,17 +22,15 @@ export default async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Test: It should return an instance of the default database
|
||||
const testGetDefaultDatabase = () => {
|
||||
databaseClient.getDefaultDatabase();
|
||||
// Test: It should return the app database
|
||||
const testGetAppDatabase = () => {
|
||||
return DatabaseManager.appDatabase?.database;
|
||||
};
|
||||
|
||||
// Test: It should creates a new server connection
|
||||
const testNewServerConnection = async () => {
|
||||
await databaseClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
await DatabaseManager.createServerDatabase({
|
||||
config: {
|
||||
dbName: 'community mattermost',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'https://comm4.mattermost.com',
|
||||
@@ -37,38 +39,44 @@ export default async () => {
|
||||
};
|
||||
|
||||
// Test: It should return the current active server database
|
||||
const testGetActiveServerConnection = () => {
|
||||
// const activeServer = DatabaseManager.getActiveServerDatabase();
|
||||
const testGetActiveServerConnection = async () => {
|
||||
return DatabaseManager.getActiveServerDatabase();
|
||||
};
|
||||
|
||||
// Test: It should set the current active server database to the provided server url.
|
||||
const testSetActiveServerConnection = async () => {
|
||||
await databaseClient.setActiveServerDatabase('https://comm4.mattermost.com');
|
||||
await DatabaseManager.setActiveServerDatabase('https://comm4.mattermost.com');
|
||||
};
|
||||
|
||||
// Test: It should return database instance(s) if there are valid server urls in the provided list.
|
||||
const testRetrieveAllDatabaseConnections = async () => {
|
||||
await databaseClient.retrieveDatabaseInstances([
|
||||
'https://xunity2.mattermost.com',
|
||||
'https://comm5.mattermost.com',
|
||||
'https://comm4.mattermost.com',
|
||||
]);
|
||||
const database = DatabaseManager.appDatabase?.database;
|
||||
const servers = (await database?.collections.get(MM_TABLES.APP.SERVERS).
|
||||
query(Q.where(
|
||||
'url',
|
||||
Q.oneOf([
|
||||
'https://xunity2.mattermost.com',
|
||||
'https://comm5.mattermost.com',
|
||||
'https://comm4.mattermost.com',
|
||||
]),
|
||||
)).fetch()) as IServers[];
|
||||
return servers;
|
||||
};
|
||||
|
||||
// Test: It should delete the associated *.db file for this server url
|
||||
const testDeleteSQLFile = async () => {
|
||||
await databaseClient.deleteDatabase('https://comm4.mattermost.com');
|
||||
await DatabaseManager.deleteServerDatabase('https://comm4.mattermost.com');
|
||||
};
|
||||
|
||||
// Test: It should wipe out the databases folder under the documents direction on Android and in the shared directory for the AppGroup on iOS
|
||||
const testFactoryReset = async () => {
|
||||
await databaseClient.factoryReset(true);
|
||||
await DatabaseManager.factoryReset(true);
|
||||
};
|
||||
|
||||
// NOTE : Comment and test the below functions one at a time. It starts with creating a default database and ends with a factory reset.
|
||||
|
||||
testAppGroupDirectory();
|
||||
testGetDefaultDatabase();
|
||||
testGetAppDatabase();
|
||||
await testNewServerConnection();
|
||||
testGetActiveServerConnection();
|
||||
await testSetActiveServerConnection();
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, json} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {GLOBAL} = MM_TABLES.DEFAULT;
|
||||
const {GLOBAL} = MM_TABLES.APP;
|
||||
|
||||
// TODO : add TS definitions to sanitizer function signature.
|
||||
|
||||
@@ -15,7 +15,7 @@ const {GLOBAL} = MM_TABLES.DEFAULT;
|
||||
* data type. It will hold information that applies to the whole app ( e.g. sidebar settings for tablets)
|
||||
*/
|
||||
export default class Global extends Model {
|
||||
/** table (entity name) : global */
|
||||
/** table (name) : global */
|
||||
static table = GLOBAL;
|
||||
|
||||
/** name : The label/key to use to retrieve the special 'value' */
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export {default as App} from './app';
|
||||
export {default as Info} from './info';
|
||||
export {default as Global} from './global';
|
||||
export {default as Servers} from './servers';
|
||||
@@ -6,15 +6,15 @@ import {field} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {APP} = MM_TABLES.DEFAULT;
|
||||
const {INFO} = MM_TABLES.APP;
|
||||
|
||||
/**
|
||||
* The App model will hold information - such as the version number, build number and creation date -
|
||||
* for the Mattermost mobile app.
|
||||
*/
|
||||
export default class App extends Model {
|
||||
/** table (entity name) : app */
|
||||
static table = APP;
|
||||
export default class Info extends Model {
|
||||
/** table (name) : info */
|
||||
static table = INFO;
|
||||
|
||||
/** build_number : Build number for the app */
|
||||
@field('build_number') buildNumber!: string;
|
||||
@@ -6,14 +6,14 @@ import {field} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {SERVERS} = MM_TABLES.DEFAULT;
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
|
||||
/**
|
||||
* The Server model will help us to identify the various servers a user will log in; in the context of
|
||||
* multi-server support system. The db_path field will hold the App-Groups file-path
|
||||
*/
|
||||
export default class Servers extends Model {
|
||||
/** table (entity name) : servers */
|
||||
/** table (name) : servers */
|
||||
static table = SERVERS;
|
||||
|
||||
/** db_path : The file path where the database is stored */
|
||||
@@ -6,16 +6,16 @@ import {children, field, immutableRelation, lazy} from '@nozbe/watermelondb/deco
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import ChannelInfo from '@typings/database/channel_info';
|
||||
import ChannelMembership from '@typings/database/channel_membership';
|
||||
import Draft from '@typings/database/draft';
|
||||
import GroupsInChannel from '@typings/database/groups_in_channel';
|
||||
import MyChannel from '@typings/database/my_channel';
|
||||
import MyChannelSettings from '@typings/database/my_channel_settings';
|
||||
import Post from '@typings/database/post';
|
||||
import PostsInChannel from '@typings/database/posts_in_channel';
|
||||
import Team from '@typings/database/team';
|
||||
import User from '@typings/database/user';
|
||||
import ChannelInfo from '@typings/database/models/servers/channel_info';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import MyChannel from '@typings/database/models/servers/my_channel';
|
||||
import MyChannelSettings from '@typings/database/models/servers/my_channel_settings';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import PostsInChannel from '@typings/database/models/servers/posts_in_channel';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
@@ -35,10 +35,10 @@ const {
|
||||
* The Channel model represents a channel in the Mattermost app.
|
||||
*/
|
||||
export default class Channel extends Model {
|
||||
/** table (entity name) : Channel */
|
||||
/** table (name) : Channel */
|
||||
static table = CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL is associated with only one CHANNEL_INFO (relationship is 1:1) */
|
||||
@@ -123,7 +123,7 @@ export default class Channel extends Model {
|
||||
/** creator : The USER who created this CHANNEL*/
|
||||
@immutableRelation(USER, 'creator_id') creator!: Relation<User>;
|
||||
|
||||
/** info : Query returning extra information about this channel from entity CHANNEL_INFO */
|
||||
/** info : Query returning extra information about this channel from CHANNEL_INFO table */
|
||||
@lazy info = this.collections.get(CHANNEL_INFO).query(Q.on(CHANNEL, 'id', this.id)) as Query<ChannelInfo>;
|
||||
|
||||
/** membership : Query returning the membership data for the current user if it belongs to this channel */
|
||||
|
||||
@@ -6,20 +6,20 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
|
||||
const {CHANNEL, CHANNEL_INFO} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* ChannelInfo is an extension of the information contained in the Channel entity.
|
||||
* ChannelInfo is an extension of the information contained in the Channel table.
|
||||
* In a Separation of Concerns approach, ChannelInfo will provide additional information about a channel but on a more
|
||||
* specific level.
|
||||
*/
|
||||
export default class ChannelInfo extends Model {
|
||||
/** table (entity name) : ChannelInfo */
|
||||
/** table (name) : ChannelInfo */
|
||||
static table = CHANNEL_INFO;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL is associated with only one CHANNEL_INFO (relationship is 1:1) */
|
||||
@@ -44,6 +44,6 @@ export default class ChannelInfo extends Model {
|
||||
/** purpose: The intention behind this channel */
|
||||
@field('purpose') purpose!: string;
|
||||
|
||||
/** channel : The lazy query property to the record from entity CHANNEL */
|
||||
/** channel : The lazy query property to the record from CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<Channel>;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import {Q, Query, Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, lazy} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import User from '@typings/database/user';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {CHANNEL, CHANNEL_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -16,10 +16,10 @@ const {CHANNEL, CHANNEL_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
* channels ( N:N relationship between model Users and model Channel)
|
||||
*/
|
||||
export default class ChannelMembership extends Model {
|
||||
/** table (entity name) : ChannelMembership */
|
||||
/** table (name) : ChannelMembership */
|
||||
static table = CHANNEL_MEMBERSHIP;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can have multiple USER */
|
||||
|
||||
@@ -10,7 +10,7 @@ const {CUSTOM_EMOJI} = MM_TABLES.SERVER;
|
||||
|
||||
/** The CustomEmoji model describes all the custom emojis used in the Mattermost app */
|
||||
export default class CustomEmoji extends Model {
|
||||
/** table (entity name) : CustomEmoji */
|
||||
/** table (name) : CustomEmoji */
|
||||
static table = CUSTOM_EMOJI;
|
||||
|
||||
/** name : The custom emoji's name*/
|
||||
|
||||
@@ -12,10 +12,10 @@ const {CHANNEL, DRAFT, POST} = MM_TABLES.SERVER;
|
||||
* The Draft model represents the draft state of messages in Direct/Group messages and in channels
|
||||
*/
|
||||
export default class Draft extends Model {
|
||||
/** table (entity name) : Draft */
|
||||
/** table (name) : Draft */
|
||||
static table = DRAFT;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A DRAFT can belong to only one CHANNEL */
|
||||
@@ -34,6 +34,6 @@ export default class Draft extends Model {
|
||||
/** root_id : The root_id will be empty most of the time unless the draft relates to a draft reply of a thread */
|
||||
@field('root_id') rootId!: string;
|
||||
|
||||
/** files : The files field will hold an array of file objects that have not yet been uploaded and persisted within the FILE entity */
|
||||
/** files : The files field will hold an array of file objects that have not yet been uploaded and persisted within the FILE table */
|
||||
@json('files', (rawJson) => rawJson) files!: FileInfo[];
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Post from '@typings/database/post';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
|
||||
const {FILE, POST} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -14,10 +14,10 @@ const {FILE, POST} = MM_TABLES.SERVER;
|
||||
* The File model works in pair with the Post model. It hosts information about the files shared in a Post
|
||||
*/
|
||||
export default class File extends Model {
|
||||
/** table (entity name) : File */
|
||||
/** table (name) : File */
|
||||
static table = FILE;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST has a 1:N relationship with FILE. */
|
||||
|
||||
@@ -5,9 +5,9 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
import {children, field} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import GroupMembership from '@typings/database/group_membership';
|
||||
import GroupsInChannel from '@typings/database/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/groups_in_team';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
|
||||
const {GROUP, GROUPS_IN_CHANNEL, GROUPS_IN_TEAM, GROUP_MEMBERSHIP} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -17,10 +17,10 @@ const {GROUP, GROUPS_IN_CHANNEL, GROUPS_IN_TEAM, GROUP_MEMBERSHIP} = MM_TABLES.S
|
||||
* name in the message. (e.g @mobile_team)
|
||||
*/
|
||||
export default class Group extends Model {
|
||||
/** table (entity name) : Group */
|
||||
/** table (name) : Group */
|
||||
static table = GROUP;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A GROUP has a 1:N relationship with GROUPS_IN_CHANNEL */
|
||||
|
||||
@@ -6,8 +6,8 @@ import {field, immutableRelation, lazy} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Group from '@typings/database/group';
|
||||
import User from '@typings/database/user';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {GROUP, GROUP_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -16,10 +16,10 @@ const {GROUP, GROUP_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
* groups (relationship type N:N)
|
||||
*/
|
||||
export default class GroupMembership extends Model {
|
||||
/** table (entity name) : GroupMembership */
|
||||
/** table (name) : GroupMembership */
|
||||
static table = GROUP_MEMBERSHIP;
|
||||
|
||||
/** associations : Describes every relationship to this entity */
|
||||
/** associations : Describes every relationship to this table */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A GROUP can have multiple users in it */
|
||||
|
||||
@@ -6,8 +6,8 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import Group from '@typings/database/group';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
|
||||
const {GROUP, GROUPS_IN_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {GROUP, GROUPS_IN_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
* The GroupsInChannel links the Channel model with the Group model
|
||||
*/
|
||||
export default class GroupsInChannel extends Model {
|
||||
/** table (entity name) : GroupsInChannel */
|
||||
/** table (name) : GroupsInChannel */
|
||||
static table = GROUPS_IN_CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A GROUP can be associated with multiple GROUPS_IN_CHANNEL (relationship is 1:N) */
|
||||
|
||||
@@ -5,9 +5,9 @@ import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import Group from '@typings/database/group';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
|
||||
const {GROUP, GROUPS_IN_TEAM, TEAM} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {GROUP, GROUPS_IN_TEAM, TEAM} = MM_TABLES.SERVER;
|
||||
* The GroupsInTeam links the Team model with the Group model
|
||||
*/
|
||||
export default class GroupsInTeam extends Model {
|
||||
/** table (entity name) : GroupsInTeam */
|
||||
/** table (name) : GroupsInTeam */
|
||||
static table = GROUPS_IN_TEAM;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** GroupsInTeam can belong to only one Group */
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
@@ -14,13 +14,13 @@ const {CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
* MyChannel is an extension of the Channel model but it lists only the Channels the app's user belongs to
|
||||
*/
|
||||
export default class MyChannel extends Model {
|
||||
/** table (entity name) : MyChannel */
|
||||
/** table (name) : MyChannel */
|
||||
static table = MY_CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can be associated to only one record from entity MY_CHANNEL (relationship is 1:1) */
|
||||
/** A CHANNEL can be associated to only one record from the MY_CHANNEL table (relationship is 1:1) */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
};
|
||||
|
||||
@@ -42,6 +42,6 @@ export default class MyChannel extends Model {
|
||||
/** roles : The user's privileges on this channel */
|
||||
@field('roles') roles!: string;
|
||||
|
||||
/** channel : The relation pointing to entity CHANNEL */
|
||||
/** channel : The relation pointing to the CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<Channel>;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {CHANNEL, MY_CHANNEL_SETTINGS} = MM_TABLES.SERVER;
|
||||
@@ -15,10 +15,10 @@ const {CHANNEL, MY_CHANNEL_SETTINGS} = MM_TABLES.SERVER;
|
||||
* the channel this user belongs to.
|
||||
*/
|
||||
export default class MyChannelSettings extends Model {
|
||||
/** table (entity name) : MyChannelSettings */
|
||||
/** table (name) : MyChannelSettings */
|
||||
static table = MY_CHANNEL_SETTINGS;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL is related to only one MY_CHANNEL_SETTINGS (relationship is 1:1) */
|
||||
@@ -31,6 +31,6 @@ export default class MyChannelSettings extends Model {
|
||||
/** notify_props : Configurations with regards to this channel */
|
||||
@json('notify_props', (rawJson) => rawJson) notifyProps!: NotifyProps;
|
||||
|
||||
/** channel : The relation pointing to entity CHANNEL */
|
||||
/** channel : The relation pointing to the CHANNEL table */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<Channel>;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, relation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
|
||||
const {TEAM, MY_TEAM} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -14,10 +14,10 @@ const {TEAM, MY_TEAM} = MM_TABLES.SERVER;
|
||||
* MyTeam represents only the teams that the current user belongs to
|
||||
*/
|
||||
export default class MyTeam extends Model {
|
||||
/** table (entity name) : MyTeam */
|
||||
/** table (name) : MyTeam */
|
||||
static table = MY_TEAM;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** TEAM and MY_TEAM have a 1:1 relationship. */
|
||||
@@ -33,9 +33,9 @@ export default class MyTeam extends Model {
|
||||
/** roles : The different permissions that this user has in the team, concatenated together with comma to form a single string. */
|
||||
@field('roles') roles!: string;
|
||||
|
||||
/** team_id : The foreign key of the 'parent' Team entity */
|
||||
/** team_id : The foreign key of the 'parent' Team record */
|
||||
@field('team_id') teamId!: string;
|
||||
|
||||
/** team : The relation to the entity TEAM, that this user belongs to */
|
||||
/** team : The relation to the TEAM, that this user belongs to */
|
||||
@relation(MY_TEAM, 'team_id') team!: Relation<Team>;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import {children, field, immutableRelation, json, lazy} from '@nozbe/watermelond
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import Draft from '@typings/database/draft';
|
||||
import File from '@typings/database/file';
|
||||
import PostInThread from '@typings/database/posts_in_thread';
|
||||
import PostMetadata from '@typings/database/post_metadata';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import User from '@typings/database/user';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import File from '@typings/database/models/servers/file';
|
||||
import PostInThread from '@typings/database/models/servers/posts_in_thread';
|
||||
import PostMetadata from '@typings/database/models/servers/post_metadata';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {CHANNEL, DRAFT, FILE, POST, POSTS_IN_THREAD, POST_METADATA, REACTION, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -20,10 +20,10 @@ const {CHANNEL, DRAFT, FILE, POST, POSTS_IN_THREAD, POST_METADATA, REACTION, USE
|
||||
* The Post model is the building block of communication in the Mattermost app.
|
||||
*/
|
||||
export default class Post extends Model {
|
||||
/** table (entity name) : Post */
|
||||
/** table (name) : Post */
|
||||
static table = POST;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can have multiple POST. (relationship is 1:N) */
|
||||
|
||||
@@ -7,7 +7,7 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {PostMetadataData, PostMetadataType} from '@typings/database/database';
|
||||
import Post from '@typings/database/post';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
|
||||
const {POST, POST_METADATA} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {POST, POST_METADATA} = MM_TABLES.SERVER;
|
||||
* PostMetadata provides additional information on a POST
|
||||
*/
|
||||
export default class PostMetadata extends Model {
|
||||
/** table (entity name) : PostMetadata */
|
||||
/** table (name) : PostMetadata */
|
||||
static table = POST_METADATA;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST can have multiple POST_METADATA.(relationship is 1:N)*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
|
||||
const {CHANNEL, POSTS_IN_CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {CHANNEL, POSTS_IN_CHANNEL} = MM_TABLES.SERVER;
|
||||
* gaps in between for an efficient user reading experience of posts.
|
||||
*/
|
||||
export default class PostsInChannel extends Model {
|
||||
/** table (entity name) : PostsInChannel */
|
||||
/** table (name) : PostsInChannel */
|
||||
static table = POSTS_IN_CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CHANNEL can have multiple POSTS_IN_CHANNEL. (relationship is 1:N)*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Post from '@typings/database/post';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
|
||||
const {POST, POSTS_IN_THREAD} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {POST, POSTS_IN_THREAD} = MM_TABLES.SERVER;
|
||||
* gaps in between for an efficient user reading experience for threads.
|
||||
*/
|
||||
export default class PostsInThread extends Model {
|
||||
/** table (entity name) : PostsInThread */
|
||||
/** table (name) : PostsInThread */
|
||||
static table = POSTS_IN_THREAD;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST can have multiple POSTS_IN_THREAD.(relationship is 1:N)*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import User from '@typings/database/user';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {PREFERENCE, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {PREFERENCE, USER} = MM_TABLES.SERVER;
|
||||
* This includes settings about the account, the themes, etc.
|
||||
*/
|
||||
export default class Preference extends Model {
|
||||
/** table (entity name) : Preference */
|
||||
/** table (name) : Preference */
|
||||
static table = PREFERENCE;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A USER can have multiple PREFERENCE.(relationship is 1:N)*/
|
||||
|
||||
@@ -6,8 +6,8 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Post from '@typings/database/post';
|
||||
import User from '@typings/database/user';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {POST, REACTION, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {POST, REACTION, USER} = MM_TABLES.SERVER;
|
||||
* The Reaction Model is used to present the reactions a user had on a particular post
|
||||
*/
|
||||
export default class Reaction extends Model {
|
||||
/** table (entity name) : Reaction */
|
||||
/** table (name) : Reaction */
|
||||
static table = REACTION;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A POST can have multiple REACTION. (relationship is 1:N) */
|
||||
|
||||
@@ -10,7 +10,7 @@ const {ROLE} = MM_TABLES.SERVER;
|
||||
|
||||
/** The Role model will describe the set of permissions for each role */
|
||||
export default class Role extends Model {
|
||||
/** table (entity name) : Role */
|
||||
/** table (name) : Role */
|
||||
static table = ROLE;
|
||||
|
||||
/** name : The role's name */
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
|
||||
const {SLASH_COMMAND, TEAM} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -14,10 +14,10 @@ const {SLASH_COMMAND, TEAM} = MM_TABLES.SERVER;
|
||||
* The SlashCommand model describes the commands of the various commands available in each team.
|
||||
*/
|
||||
export default class SlashCommand extends Model {
|
||||
/** table (entity name) : SlashCommand */
|
||||
/** table (name) : SlashCommand */
|
||||
static table = SLASH_COMMAND;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A TEAM can have multiple slash commands. (relationship is 1:N) */
|
||||
|
||||
@@ -14,7 +14,7 @@ const {SYSTEM} = MM_TABLES.SERVER;
|
||||
* custom data (e.g. recent emoji used)
|
||||
*/
|
||||
export default class System extends Model {
|
||||
/** table (entity name) : System */
|
||||
/** table (name) : System */
|
||||
static table = SYSTEM;
|
||||
|
||||
/** name : The name or key value for the config */
|
||||
|
||||
@@ -6,13 +6,13 @@ import {children, field, lazy} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import GroupsInTeam from '@typings/database/groups_in_team';
|
||||
import MyTeam from '@typings/database/my_team';
|
||||
import SlashCommand from '@typings/database/slash_command';
|
||||
import TeamChannelHistory from '@typings/database/team_channel_history';
|
||||
import TeamMembership from '@typings/database/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/team_search_history';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
import MyTeam from '@typings/database/models/servers/my_team';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import TeamChannelHistory from '@typings/database/models/servers/team_channel_history';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/models/servers/team_search_history';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
@@ -29,10 +29,10 @@ const {
|
||||
* A Team houses and enables communication to happen across channels and users.
|
||||
*/
|
||||
export default class Team extends Model {
|
||||
/** table (entity name) : Team */
|
||||
/** table (name) : Team */
|
||||
static table = TEAM;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A TEAM has a 1:N relationship with CHANNEL. A TEAM can possess multiple channels */
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
|
||||
const {TEAM, TEAM_CHANNEL_HISTORY} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {TEAM, TEAM_CHANNEL_HISTORY} = MM_TABLES.SERVER;
|
||||
* by the user.
|
||||
*/
|
||||
export default class TeamChannelHistory extends Model {
|
||||
/** table (entity name) : TeamChannelHistory */
|
||||
/** table (name) : TeamChannelHistory */
|
||||
static table = TEAM_CHANNEL_HISTORY;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A TEAM and TEAM_CHANNEL_HISTORY share a 1:1 relationship */
|
||||
|
||||
@@ -6,8 +6,8 @@ import {field, immutableRelation, lazy} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import User from '@typings/database/user';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {TEAM, TEAM_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -16,10 +16,10 @@ const {TEAM, TEAM_MEMBERSHIP, USER} = MM_TABLES.SERVER;
|
||||
* teams (relationship type N:N)
|
||||
*/
|
||||
export default class TeamMembership extends Model {
|
||||
/** table (entity name) : TeamMembership */
|
||||
/** table (name) : TeamMembership */
|
||||
static table = TEAM_MEMBERSHIP;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** TEAM and TEAM_MEMBERSHIP share a 1:N relationship; USER can be part of multiple teams */
|
||||
|
||||
@@ -6,7 +6,7 @@ import {field, immutableRelation, text} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Team from '@typings/database/team';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
|
||||
const {TEAM, TEAM_SEARCH_HISTORY} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -15,10 +15,10 @@ const {TEAM, TEAM_SEARCH_HISTORY} = MM_TABLES.SERVER;
|
||||
* at team level in the app.
|
||||
*/
|
||||
export default class TeamSearchHistory extends Model {
|
||||
/** table (entity name) : TeamSearchHistory */
|
||||
/** table (name) : TeamSearchHistory */
|
||||
static table = TEAM_SEARCH_HISTORY;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A TEAM can have multiple search terms */
|
||||
|
||||
@@ -12,7 +12,7 @@ const {TERMS_OF_SERVICE} = MM_TABLES.SERVER;
|
||||
* The model for Terms of Service
|
||||
*/
|
||||
export default class TermsOfService extends Model {
|
||||
/** table (entity name) : TermsOfService */
|
||||
/** table (name) : TermsOfService */
|
||||
static table = TERMS_OF_SERVICE;
|
||||
|
||||
/** accepted_at : the date the term has been accepted */
|
||||
|
||||
@@ -5,13 +5,13 @@ import {children, field, json} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import ChannelMembership from '@typings/database/channel_membership';
|
||||
import GroupMembership from '@typings/database/group_membership';
|
||||
import Post from '@typings/database/post';
|
||||
import Preference from '@typings/database/preference';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import TeamMembership from '@typings/database/team_membership';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import Preference from '@typings/database/models/servers/preference';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
@@ -25,14 +25,14 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* The User model represents the 'USER' entity and its relationship to other
|
||||
* The User model represents the 'USER' table and its relationship to other
|
||||
* shareholders in the app.
|
||||
*/
|
||||
export default class User extends Model {
|
||||
/** table (entity name) : User */
|
||||
/** table (name) : User */
|
||||
static table = USER;
|
||||
|
||||
/** associations : Describes every relationship to this entity. */
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** USER has a 1:N relationship with CHANNEL. A user can create multiple channels */
|
||||
|
||||
19
app/database/operator/app_data_operator/comparator/index.ts
Normal file
19
app/database/operator/app_data_operator/comparator/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Info from '@typings/database/models/app/info';
|
||||
import {RawInfo, RawGlobal, RawServers} from '@typings/database/database';
|
||||
import Global from '@typings/database/models/app/global';
|
||||
import Servers from '@typings/database/models/app/servers';
|
||||
|
||||
export const isRecordInfoEqualToRaw = (record: Info, raw: RawInfo) => {
|
||||
return (raw.build_number === record.buildNumber && raw.version_number === record.versionNumber);
|
||||
};
|
||||
|
||||
export const isRecordGlobalEqualToRaw = (record: Global, raw: RawGlobal) => {
|
||||
return raw.name === record.name && raw.value === record.value;
|
||||
};
|
||||
|
||||
export const isRecordServerEqualToRaw = (record: Servers, raw: RawServers) => {
|
||||
return raw.url === record.url && raw.db_path === record.dbPath;
|
||||
};
|
||||
136
app/database/operator/app_data_operator/index.test.ts
Normal file
136
app/database/operator/app_data_operator/index.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordInfoEqualToRaw,
|
||||
isRecordGlobalEqualToRaw,
|
||||
isRecordServerEqualToRaw,
|
||||
} from '@database/operator/app_data_operator/comparator';
|
||||
import {
|
||||
transformInfoRecord,
|
||||
transformGlobalRecord,
|
||||
transformServersRecord,
|
||||
} from '@database/operator/app_data_operator/transformers';
|
||||
import {RawGlobal, RawServers} from '@typings/database/database';
|
||||
|
||||
describe('** APP DATA OPERATOR **', () => {
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init([]);
|
||||
});
|
||||
|
||||
it('=> HandleApp: should write to INFO table', async () => {
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
const appOperator = DatabaseManager.appDatabase?.operator;
|
||||
expect(appDatabase).toBeTruthy();
|
||||
expect(appOperator).toBeTruthy();
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(appOperator as any, 'handleRecords');
|
||||
|
||||
await appOperator?.handleInfo({
|
||||
info: [
|
||||
{
|
||||
build_number: 'build-10x',
|
||||
created_at: 1,
|
||||
version_number: 'version-10',
|
||||
},
|
||||
{
|
||||
build_number: 'build-11y',
|
||||
created_at: 1,
|
||||
version_number: 'version-11',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'version_number',
|
||||
transformer: transformInfoRecord,
|
||||
findMatchingRecordBy: isRecordInfoEqualToRaw,
|
||||
createOrUpdateRawValues: [
|
||||
{
|
||||
build_number: 'build-10x',
|
||||
created_at: 1,
|
||||
version_number: 'version-10',
|
||||
},
|
||||
{
|
||||
build_number: 'build-11y',
|
||||
created_at: 1,
|
||||
version_number: 'version-11',
|
||||
},
|
||||
],
|
||||
tableName: 'Info',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGlobal: should write to GLOBAL table', async () => {
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
const appOperator = DatabaseManager.appDatabase?.operator;
|
||||
expect(appDatabase).toBeTruthy();
|
||||
expect(appOperator).toBeTruthy();
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(appOperator as any, 'handleRecords');
|
||||
const global: RawGlobal[] = [{name: 'global-1-name', value: 'global-1-value'}];
|
||||
|
||||
await appOperator?.handleGlobal({
|
||||
global,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordGlobalEqualToRaw,
|
||||
fieldName: 'name',
|
||||
transformer: transformGlobalRecord,
|
||||
createOrUpdateRawValues: global,
|
||||
tableName: 'Global',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleServers: should write to SERVERS table', async () => {
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
const appOperator = DatabaseManager.appDatabase?.operator;
|
||||
expect(appDatabase).toBeTruthy();
|
||||
expect(appOperator).toBeTruthy();
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(appOperator as any, 'handleRecords');
|
||||
|
||||
const servers: RawServers[] = [
|
||||
{
|
||||
db_path: 'server.db',
|
||||
display_name: 'community',
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
];
|
||||
|
||||
await appOperator?.handleServers({
|
||||
servers,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'url',
|
||||
transformer: transformServersRecord,
|
||||
findMatchingRecordBy: isRecordServerEqualToRaw,
|
||||
createOrUpdateRawValues: [
|
||||
{
|
||||
db_path: 'server.db',
|
||||
display_name: 'community',
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
],
|
||||
tableName: 'Servers',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
83
app/database/operator/app_data_operator/index.ts
Normal file
83
app/database/operator/app_data_operator/index.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {
|
||||
isRecordInfoEqualToRaw,
|
||||
isRecordGlobalEqualToRaw,
|
||||
isRecordServerEqualToRaw,
|
||||
} from '@database/operator/app_data_operator/comparator';
|
||||
import {
|
||||
transformInfoRecord,
|
||||
transformGlobalRecord,
|
||||
transformServersRecord,
|
||||
} from '@database/operator/app_data_operator/transformers';
|
||||
import BaseDataOperator from '@database/operator/base_data_operator';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {
|
||||
HandleInfoArgs,
|
||||
HandleGlobalArgs,
|
||||
HandleServersArgs,
|
||||
} from '@typings/database/database';
|
||||
|
||||
const {APP: {INFO, GLOBAL, SERVERS}} = MM_TABLES;
|
||||
|
||||
export default class AppDataOperator extends BaseDataOperator {
|
||||
handleInfo = async ({info, prepareRecordsOnly = true}: HandleInfoArgs) => {
|
||||
if (!info.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleInfo',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'version_number',
|
||||
findMatchingRecordBy: isRecordInfoEqualToRaw,
|
||||
transformer: transformInfoRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: info, key: 'version_number'}),
|
||||
tableName: INFO,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleGlobal = async ({global, prepareRecordsOnly = true}: HandleGlobalArgs) => {
|
||||
if (!global.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleGlobal',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordGlobalEqualToRaw,
|
||||
transformer: transformGlobalRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: global, key: 'name'}),
|
||||
tableName: GLOBAL,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleServers = async ({servers, prepareRecordsOnly = true}: HandleServersArgs) => {
|
||||
if (!servers.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleServers',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'url',
|
||||
findMatchingRecordBy: isRecordServerEqualToRaw,
|
||||
transformer: transformServersRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: servers, key: 'display_name'}),
|
||||
tableName: SERVERS,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers';
|
||||
import Info from '@typings/database/models/app/info';
|
||||
import {TransformerArgs, RawInfo, RawGlobal, RawServers} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import Global from '@typings/database/models/app/global';
|
||||
import Servers from '@typings/database/models/app/servers';
|
||||
|
||||
const {INFO, GLOBAL, SERVERS} = MM_TABLES.APP;
|
||||
|
||||
/**
|
||||
* transformInfoRecord: Prepares a record of the APP database 'Info' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformInfoRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawInfo;
|
||||
const record = value.record as Info;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (app: Info) => {
|
||||
app._raw.id = isCreateAction ? app.id : record.id;
|
||||
app.buildNumber = raw?.build_number;
|
||||
app.createdAt = raw?.created_at;
|
||||
app.versionNumber = raw?.version_number;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
fieldsMapper,
|
||||
tableName: INFO,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* transformGlobalRecord: Prepares a record of the APP database 'Global' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformGlobalRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawGlobal;
|
||||
const record = value.record as Global;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (global: Global) => {
|
||||
global._raw.id = isCreateAction ? global.id : record.id;
|
||||
global.name = raw?.name;
|
||||
global.value = raw?.value;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
fieldsMapper,
|
||||
tableName: GLOBAL,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* transformServersRecord: Prepares a record of the APP database 'Servers' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformServersRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawServers;
|
||||
const record = value.record as Servers;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const fieldsMapper = (servers: Servers) => {
|
||||
servers._raw.id = isCreateAction ? servers.id : record.id;
|
||||
servers.dbPath = raw?.db_path;
|
||||
servers.displayName = raw?.display_name;
|
||||
servers.mentionCount = raw?.mention_count;
|
||||
servers.unreadCount = raw?.unread_count;
|
||||
servers.url = raw?.url;
|
||||
servers.isSecured = raw?.isSecured;
|
||||
servers.lastActiveAt = raw?.lastActiveAt;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: SERVERS,
|
||||
value,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
85
app/database/operator/app_data_operator/transformers/test.ts
Normal file
85
app/database/operator/app_data_operator/transformers/test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
transformInfoRecord,
|
||||
transformGlobalRecord,
|
||||
transformServersRecord,
|
||||
} from '@database/operator/app_data_operator/transformers/index';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('** APP DATA TRANSFORMER **', () => {
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init([]);
|
||||
});
|
||||
|
||||
it('=> transformServersRecord: should return an array of type Servers', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = DatabaseManager.appDatabase?.database;
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformServersRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
db_path: 'mm-server',
|
||||
display_name: 's-displayName',
|
||||
mention_count: 1,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Servers');
|
||||
});
|
||||
|
||||
it('=> transformInfoRecord: should return an array of type Info', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = DatabaseManager.appDatabase?.database;
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformInfoRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
build_number: 'build-7',
|
||||
created_at: 1,
|
||||
version_number: 'v-1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Info');
|
||||
});
|
||||
|
||||
it('=> transformGlobalRecord: should return an array of type Global', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = DatabaseManager.appDatabase?.database;
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformGlobalRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {name: 'g-n1', value: 'g-v1'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Global');
|
||||
});
|
||||
});
|
||||
239
app/database/operator/base_data_operator/index.ts
Normal file
239
app/database/operator/base_data_operator/index.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {
|
||||
getRangeOfValues,
|
||||
getValidRecordsForUpdate,
|
||||
retrieveRecords,
|
||||
} from '@database/operator/utils/general';
|
||||
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import type {
|
||||
HandleRecordsArgs,
|
||||
OperationArgs,
|
||||
ProcessRecordResults,
|
||||
ProcessRecordsArgs,
|
||||
RawValue,
|
||||
RecordPair,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
export interface BaseDataOperatorType {
|
||||
database: Database;
|
||||
handleRecords: ({findMatchingRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs) => Promise<Model[]>;
|
||||
processRecords: ({createOrUpdateRawValues, deleteRawValues, tableName, findMatchingRecordBy, fieldName}: ProcessRecordsArgs) => Promise<ProcessRecordResults>;
|
||||
batchRecords: (models: Model[]) => Promise<void>;
|
||||
prepareRecords: ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs) => Promise<Model[]>;
|
||||
}
|
||||
|
||||
export default class BaseDataOperator {
|
||||
database: Database;
|
||||
|
||||
constructor(database: Database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* processRecords: This method weeds out duplicates entries. It may happen that we do multiple inserts for
|
||||
* the same value. Hence, prior to that we query the database and pick only those values that are 'new' from the 'Raw' array.
|
||||
* @param {ProcessRecordsArgs} inputsArg
|
||||
* @param {RawValue[]} inputsArg.createOrUpdateRawValues
|
||||
* @param {string} inputsArg.tableName
|
||||
* @param {string} inputsArg.fieldName
|
||||
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.findMatchingRecordBy
|
||||
* @returns {Promise<{ProcessRecordResults}>}
|
||||
*/
|
||||
processRecords = async ({createOrUpdateRawValues, deleteRawValues = [], tableName, findMatchingRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults> => {
|
||||
const getRecords = async (rawValues : RawValue[]) => {
|
||||
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
|
||||
const columnValues: string[] = getRangeOfValues({
|
||||
fieldName,
|
||||
raws: rawValues,
|
||||
});
|
||||
|
||||
if (!columnValues.length && rawValues.length) {
|
||||
throw new DataOperatorException(
|
||||
`Invalid "fieldName" or "tableName" has been passed to the processRecords method for tableName ${tableName} fieldName ${fieldName}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!rawValues.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const existingRecords = await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName,
|
||||
condition: Q.where(fieldName, Q.oneOf(columnValues)),
|
||||
});
|
||||
|
||||
return existingRecords;
|
||||
};
|
||||
|
||||
const createRaws: RecordPair[] = [];
|
||||
const updateRaws: RecordPair[] = [];
|
||||
|
||||
// for delete flow
|
||||
const deleteRaws = await getRecords(deleteRawValues);
|
||||
|
||||
// for create or update flow
|
||||
const createOrUpdateRaws = await getRecords(createOrUpdateRawValues);
|
||||
if (createOrUpdateRawValues.length > 0) {
|
||||
createOrUpdateRawValues.forEach((newElement: RawValue) => {
|
||||
const findIndex = createOrUpdateRaws.findIndex((existing) => {
|
||||
return findMatchingRecordBy(existing, newElement);
|
||||
});
|
||||
|
||||
// We found a record in the database that matches this element; hence, we'll proceed for an UPDATE operation
|
||||
if (findIndex > -1) {
|
||||
const existingRecord = createOrUpdateRaws[findIndex];
|
||||
|
||||
// Some raw value has an update_at field. We'll proceed to update only if the update_at value is different from the record's value in database
|
||||
const updateRecords = getValidRecordsForUpdate({
|
||||
tableName,
|
||||
existingRecord,
|
||||
newValue: newElement,
|
||||
});
|
||||
|
||||
updateRaws.push(updateRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// This RawValue is not present in the database; hence, we need to create it
|
||||
createRaws.push({record: undefined, raw: newElement});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
createRaws,
|
||||
updateRaws,
|
||||
deleteRaws,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareRecords: Utility method that actually calls the operators for the handlers
|
||||
* @param {OperationArgs} prepareRecord
|
||||
* @param {string} prepareRecord.tableName
|
||||
* @param {RawValue[]} prepareRecord.createRaws
|
||||
* @param {RawValue[]} prepareRecord.updateRaws
|
||||
* @param {Model[]} prepareRecord.deleteRaws
|
||||
* @param {(TransformerArgs) => Promise<Model>;} prepareRecord.composer
|
||||
* @throws {DataOperatorException}
|
||||
* @returns {Promise<Model[]>}
|
||||
*/
|
||||
prepareRecords = async ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs) => {
|
||||
if (!this.database) {
|
||||
throw new DataOperatorException('Database not defined');
|
||||
}
|
||||
|
||||
let preparedRecords: Promise<Model>[] = [];
|
||||
|
||||
// create operation
|
||||
if (createRaws?.length) {
|
||||
const recordPromises = createRaws.map(
|
||||
(createRecord: RecordPair) => {
|
||||
return transformer({
|
||||
database: this.database,
|
||||
tableName,
|
||||
value: createRecord,
|
||||
action: OperationType.CREATE,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
preparedRecords = preparedRecords.concat(recordPromises);
|
||||
}
|
||||
|
||||
// update operation
|
||||
if (updateRaws?.length) {
|
||||
const recordPromises = updateRaws.map(
|
||||
(updateRecord: RecordPair) => {
|
||||
return transformer({
|
||||
database: this.database,
|
||||
tableName,
|
||||
value: updateRecord,
|
||||
action: OperationType.UPDATE,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
preparedRecords = preparedRecords.concat(recordPromises);
|
||||
}
|
||||
|
||||
const results = await Promise.all(preparedRecords);
|
||||
|
||||
if (deleteRaws?.length) {
|
||||
deleteRaws.forEach((deleteRecord) => {
|
||||
results.push(deleteRecord.prepareDestroyPermanently());
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* batchRecords: Accepts an instance of Database (either Default or Server) and an array of
|
||||
* prepareCreate/prepareUpdate 'models' and executes the actions on the database.
|
||||
* @param {Array} models
|
||||
* @throws {DataOperatorException}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
batchRecords = async (models: Model[]) => {
|
||||
try {
|
||||
if (models.length > 0) {
|
||||
await this.database.action(async () => {
|
||||
await this.database.batch(...models);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new DataOperatorException('batchRecords error ', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handleRecords : Utility that processes some records' data against values already present in the database so as to avoid duplicity.
|
||||
* @param {HandleRecordsArgs} handleRecordsArgs
|
||||
* @param {(existing: Model, newElement: RawValue) => boolean} handleRecordsArgs.findMatchingRecordBy
|
||||
* @param {string} handleRecordsArgs.fieldName
|
||||
* @param {(TransformerArgs) => Promise<Model>} handleRecordsArgs.composer
|
||||
* @param {RawValue[]} handleRecordsArgs.createOrUpdateRawValues
|
||||
* @param {RawValue[]} handleRecordsArgs.deleteRawValues
|
||||
* @param {string} handleRecordsArgs.tableName
|
||||
* @returns {Promise<Model[]>}
|
||||
*/
|
||||
handleRecords = async ({findMatchingRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true}: HandleRecordsArgs) => {
|
||||
if (!createOrUpdateRawValues.length) {
|
||||
throw new DataOperatorException(
|
||||
`An empty "rawValues" array has been passed to the handleRecords method for tableName ${tableName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const {createRaws, deleteRaws, updateRaws} = await this.processRecords({
|
||||
createOrUpdateRawValues,
|
||||
deleteRawValues,
|
||||
tableName,
|
||||
findMatchingRecordBy,
|
||||
fieldName,
|
||||
});
|
||||
|
||||
let models: Model[] = [];
|
||||
models = await this.prepareRecords({
|
||||
tableName,
|
||||
createRaws,
|
||||
updateRaws,
|
||||
deleteRaws,
|
||||
transformer,
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly && models?.length) {
|
||||
await this.batchRecords(models);
|
||||
}
|
||||
|
||||
return models;
|
||||
};
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {
|
||||
isRecordAppEqualToRaw,
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordGlobalEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordServerEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareAppRecord,
|
||||
prepareCustomEmojiRecord,
|
||||
prepareGlobalRecord,
|
||||
prepareRoleRecord,
|
||||
prepareServersRecord,
|
||||
prepareSystemRecord,
|
||||
prepareTermsOfServiceRecord,
|
||||
} from '@database/operator/prepareRecords/general';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {RawGlobal, RawRole, RawServers, RawTermsOfService} from '@typings/database/database';
|
||||
import {DatabaseType, IsolatedEntities} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** DataOperator: Base Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
});
|
||||
|
||||
it('=> HandleApp: should write to APP entity', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const defaultDatabase = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(defaultDatabase).toBeTruthy();
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.APP,
|
||||
values: [
|
||||
{
|
||||
build_number: 'build-10x',
|
||||
created_at: 1,
|
||||
version_number: 'version-10',
|
||||
},
|
||||
{
|
||||
build_number: 'build-11y',
|
||||
created_at: 1,
|
||||
version_number: 'version-11',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'version_number',
|
||||
operator: prepareAppRecord,
|
||||
findMatchingRecordBy: isRecordAppEqualToRaw,
|
||||
rawValues: [
|
||||
{
|
||||
build_number: 'build-10x',
|
||||
created_at: 1,
|
||||
version_number: 'version-10',
|
||||
},
|
||||
{
|
||||
build_number: 'build-11y',
|
||||
created_at: 1,
|
||||
version_number: 'version-11',
|
||||
},
|
||||
],
|
||||
tableName: 'app',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGlobal: should write to GLOBAL entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const defaultDatabase = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(defaultDatabase).toBeTruthy();
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
const values: RawGlobal[] = [{name: 'global-1-name', value: 'global-1-value'}];
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.GLOBAL,
|
||||
values,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordGlobalEqualToRaw,
|
||||
fieldName: 'name',
|
||||
operator: prepareGlobalRecord,
|
||||
rawValues: values,
|
||||
tableName: 'global',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleServers: should write to SERVERS entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const defaultDatabase = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(defaultDatabase).toBeTruthy();
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
const values: RawServers[] = [
|
||||
{
|
||||
db_path: 'server.db',
|
||||
display_name: 'community',
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
];
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.SERVERS,
|
||||
values,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'url',
|
||||
operator: prepareServersRecord,
|
||||
findMatchingRecordBy: isRecordServerEqualToRaw,
|
||||
rawValues: [
|
||||
{
|
||||
db_path: 'server.db',
|
||||
display_name: 'community',
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
],
|
||||
tableName: 'servers',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleRole: should write to ROLE entity', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await createTestConnection({databaseName: 'base_handler', setActive: true});
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
const values: RawRole[] = [
|
||||
{
|
||||
id: 'custom-emoji-id-1',
|
||||
name: 'custom-emoji-1',
|
||||
permissions: ['custom-emoji-1'],
|
||||
},
|
||||
];
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.ROLE,
|
||||
values,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
operator: prepareRoleRecord,
|
||||
findMatchingRecordBy: isRecordRoleEqualToRaw,
|
||||
rawValues: [
|
||||
{
|
||||
id: 'custom-emoji-id-1',
|
||||
name: 'custom-emoji-1',
|
||||
permissions: ['custom-emoji-1'],
|
||||
},
|
||||
],
|
||||
tableName: 'Role',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleCustomEmojis: should write to CUSTOM_EMOJI entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'base_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.CUSTOM_EMOJI,
|
||||
values: [
|
||||
{
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
rawValues: [
|
||||
{
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
],
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
operator: prepareCustomEmojiRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSystem: should write to SYSTEM entity', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await createTestConnection({databaseName: 'base_handler', setActive: true});
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
const values = [{id: 'system-id-1', name: 'system-1', value: 'system-1'}];
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.SYSTEM,
|
||||
values,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordSystemEqualToRaw,
|
||||
fieldName: 'name',
|
||||
operator: prepareSystemRecord,
|
||||
rawValues: values,
|
||||
tableName: 'System',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTermsOfService: should write to TERMS_OF_SERVICE entity', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await createTestConnection({databaseName: 'base_handler', setActive: true});
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
const values: RawTermsOfService[] = [
|
||||
{
|
||||
id: 'tos-1',
|
||||
accepted_at: 1,
|
||||
create_at: 1613667352029,
|
||||
user_id: 'user1613667352029',
|
||||
text: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operatorClient.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.TERMS_OF_SERVICE,
|
||||
values,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordTermsOfServiceEqualToRaw,
|
||||
fieldName: 'id',
|
||||
operator: prepareTermsOfServiceRecord,
|
||||
rawValues: values,
|
||||
tableName: 'TermsOfService',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> No table name: should not call executeInDatabase if tableName is invalid', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const defaultDatabase = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(defaultDatabase).toBeTruthy();
|
||||
|
||||
await expect(
|
||||
operatorClient.handleIsolatedEntity({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
tableName: 'INVALID_TABLE_NAME',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
values: [{id: 'tos-1', accepted_at: 1}],
|
||||
}),
|
||||
).rejects.toThrow(DataOperatorException);
|
||||
});
|
||||
});
|
||||
@@ -1,448 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import DatabaseConnectionException from '@database/exceptions/database_connection_exception';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordAppEqualToRaw,
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordGlobalEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordServerEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareAppRecord,
|
||||
prepareCustomEmojiRecord,
|
||||
prepareGlobalRecord,
|
||||
prepareRoleRecord,
|
||||
prepareServersRecord,
|
||||
prepareSystemRecord,
|
||||
prepareTermsOfServiceRecord,
|
||||
} from '@database/operator/prepareRecords/general';
|
||||
import {
|
||||
getRangeOfValues,
|
||||
getRawRecordPairs,
|
||||
getUniqueRawsBy,
|
||||
getValidRecordsForUpdate,
|
||||
retrieveRecords,
|
||||
} from '@database/operator/utils/general';
|
||||
import {
|
||||
BatchOperationsArgs,
|
||||
DatabaseInstance,
|
||||
HandleEntityRecordsArgs,
|
||||
HandleIsolatedEntityArgs,
|
||||
PrepareForDatabaseArgs,
|
||||
PrepareRecordsArgs,
|
||||
ProcessInputsArgs,
|
||||
RawValue,
|
||||
RecordPair,
|
||||
} from '@typings/database/database';
|
||||
import {IsolatedEntities, OperationType} from '@typings/database/enums';
|
||||
|
||||
export interface BaseHandlerMix {
|
||||
activeDatabase: Database;
|
||||
getActiveDatabase: () => DatabaseInstance;
|
||||
setActiveDatabase: (database: Database) => void;
|
||||
handleIsolatedEntity: ({tableName, values, prepareRecordsOnly}: HandleIsolatedEntityArgs) => Model[];
|
||||
handleEntityRecords: ({findMatchingRecordBy, fieldName, operator, rawValues, tableName, prepareRecordsOnly}: HandleEntityRecordsArgs) => Promise<null | Model[]>;
|
||||
processInputs: ({rawValues, tableName, findMatchingRecordBy, fieldName}: ProcessInputsArgs) => Promise<{ createRaws: RecordPair[]; updateRaws: RecordPair[] }>;
|
||||
batchOperations: ({database, models}: BatchOperationsArgs) => Promise<void>;
|
||||
prepareRecords: ({database, tableName, createRaws, updateRaws, recordOperator}: PrepareRecordsArgs) => Promise<Model[]>;
|
||||
executeInDatabase: ({createRaws, recordOperator, tableName, updateRaws}: PrepareForDatabaseArgs) => Promise<void>;
|
||||
getDatabase: (tableName: string) => Database;
|
||||
getDefaultDatabase: () => Promise<Database>;
|
||||
getServerDatabase: () => Promise<Database>;
|
||||
}
|
||||
|
||||
class BaseHandler {
|
||||
/**
|
||||
* activeDatabase : In a multi-server configuration, this connection will be used by WebSockets and other parties to update databases other than the active one.
|
||||
* @type {DatabaseInstance}
|
||||
*/
|
||||
activeDatabase: DatabaseInstance;
|
||||
|
||||
constructor(serverDatabase: Database) {
|
||||
this.activeDatabase = serverDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* getActiveDatabase : getter for the activeDatabase
|
||||
* @returns {DatabaseInstance}
|
||||
*/
|
||||
getActiveDatabase = () => this.activeDatabase;
|
||||
|
||||
/**
|
||||
* setActiveDatabase: setter for the activeDatabase
|
||||
* @param {} database
|
||||
*/
|
||||
setActiveDatabase = (database: Database) => {
|
||||
this.activeDatabase = database;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleIsolatedEntity: Handler responsible for the Create/Update operations on the isolated entities as described
|
||||
* by the IsolatedEntities enum
|
||||
* @param {HandleIsolatedEntityArgs} isolatedEntityArgs
|
||||
* @param {IsolatedEntities} isolatedEntityArgs.tableName
|
||||
* @param {boolean} isolatedEntityArgs.prepareRecordsOnly
|
||||
* @param {RawValue} isolatedEntityArgs.values
|
||||
* @throws DataOperatorException
|
||||
* @returns {Model[]}
|
||||
*/
|
||||
handleIsolatedEntity = async ({tableName, values, prepareRecordsOnly = true}: HandleIsolatedEntityArgs) => {
|
||||
let findMatchingRecordBy;
|
||||
let fieldName;
|
||||
let operator;
|
||||
let rawValues;
|
||||
let records: Model[] = [];
|
||||
|
||||
if (!values.length) {
|
||||
throw new DataOperatorException(
|
||||
`An empty "values" array has been passed to the handleIsolatedEntity method for entity ${tableName}`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (tableName) {
|
||||
case IsolatedEntities.APP: {
|
||||
findMatchingRecordBy = isRecordAppEqualToRaw;
|
||||
fieldName = 'version_number';
|
||||
operator = prepareAppRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'version_number'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.CUSTOM_EMOJI: {
|
||||
findMatchingRecordBy = isRecordCustomEmojiEqualToRaw;
|
||||
fieldName = 'id';
|
||||
operator = prepareCustomEmojiRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'id'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.GLOBAL: {
|
||||
findMatchingRecordBy = isRecordGlobalEqualToRaw;
|
||||
fieldName = 'name';
|
||||
operator = prepareGlobalRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'name'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.ROLE: {
|
||||
findMatchingRecordBy = isRecordRoleEqualToRaw;
|
||||
fieldName = 'id';
|
||||
operator = prepareRoleRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'id'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.SERVERS: {
|
||||
findMatchingRecordBy = isRecordServerEqualToRaw;
|
||||
fieldName = 'url';
|
||||
operator = prepareServersRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'display_name'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.SYSTEM: {
|
||||
findMatchingRecordBy = isRecordSystemEqualToRaw;
|
||||
fieldName = 'name';
|
||||
operator = prepareSystemRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'name'});
|
||||
break;
|
||||
}
|
||||
case IsolatedEntities.TERMS_OF_SERVICE: {
|
||||
findMatchingRecordBy = isRecordTermsOfServiceEqualToRaw;
|
||||
fieldName = 'id';
|
||||
operator = prepareTermsOfServiceRecord;
|
||||
rawValues = getUniqueRawsBy({raws: values, key: 'id'});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new DataOperatorException(
|
||||
`handleIsolatedEntity was called with an invalid table name ${tableName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldName && findMatchingRecordBy) {
|
||||
records = await this.handleEntityRecords({
|
||||
fieldName,
|
||||
findMatchingRecordBy,
|
||||
operator,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
tableName,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleEntityRecords : Utility that processes some entities' data against values already present in the database so as to avoid duplicity.
|
||||
* @param {HandleEntityRecordsArgs} handleEntityArgs
|
||||
* @param {(existing: Model, newElement: RawValue) => boolean} handleEntityArgs.findMatchingRecordBy
|
||||
* @param {string} handleEntityArgs.fieldName
|
||||
* @param {(DataFactoryArgs) => Promise<Model>} handleEntityArgs.operator
|
||||
* @param {RawValue[]} handleEntityArgs.rawValues
|
||||
* @param {string} handleEntityArgs.tableName
|
||||
* @returns {Promise<Model[]>}
|
||||
*/
|
||||
handleEntityRecords = async ({findMatchingRecordBy, fieldName, operator, rawValues, tableName, prepareRecordsOnly = true}: HandleEntityRecordsArgs) => {
|
||||
if (!rawValues.length) {
|
||||
throw new DataOperatorException(
|
||||
`An empty "rawValues" array has been passed to the handleEntityRecords method for tableName ${tableName}`,
|
||||
);
|
||||
}
|
||||
const {createRaws, updateRaws} = await this.processInputs({
|
||||
rawValues,
|
||||
tableName,
|
||||
findMatchingRecordBy,
|
||||
fieldName,
|
||||
});
|
||||
|
||||
const database = await this.getDatabase(tableName);
|
||||
|
||||
let models: Model[] = [];
|
||||
models = await this.prepareRecords({
|
||||
database,
|
||||
tableName,
|
||||
createRaws,
|
||||
updateRaws,
|
||||
recordOperator: operator,
|
||||
});
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return models;
|
||||
}
|
||||
|
||||
if (models?.length > 0) {
|
||||
await this.batchOperations({database, models});
|
||||
}
|
||||
|
||||
return models;
|
||||
};
|
||||
|
||||
/**
|
||||
* processInputs: This method weeds out duplicates entries. It may happen that we do multiple inserts for
|
||||
* the same value. Hence, prior to that we query the database and pick only those values that are 'new' from the 'Raw' array.
|
||||
* @param {ProcessInputsArgs} inputsArg
|
||||
* @param {RawValue[]} inputsArg.rawValues
|
||||
* @param {string} inputsArg.tableName
|
||||
* @param {string} inputsArg.fieldName
|
||||
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.findMatchingRecordBy
|
||||
* @returns {Promise<{createRaws: RecordPair[], updateRaws: RecordPair[]} | {createRaws: RecordPair[], updateRaws: RecordPair[]}>}
|
||||
*/
|
||||
processInputs = async ({rawValues, tableName, findMatchingRecordBy, fieldName}: ProcessInputsArgs) => {
|
||||
// We will query an entity where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
|
||||
const columnValues: string[] = getRangeOfValues({
|
||||
fieldName,
|
||||
raws: rawValues,
|
||||
});
|
||||
|
||||
const database = await this.getDatabase(tableName);
|
||||
|
||||
const existingRecords = await retrieveRecords({
|
||||
database,
|
||||
tableName,
|
||||
condition: Q.where(fieldName, Q.oneOf(columnValues)),
|
||||
});
|
||||
|
||||
const createRaws: RecordPair[] = [];
|
||||
const updateRaws: RecordPair[] = [];
|
||||
|
||||
if (existingRecords.length > 0) {
|
||||
rawValues.forEach((newElement: RawValue) => {
|
||||
const findIndex = existingRecords.findIndex((existing) => {
|
||||
return findMatchingRecordBy(existing, newElement);
|
||||
});
|
||||
|
||||
// We found a record in the database that matches this element; hence, we'll proceed for an UPDATE operation
|
||||
if (findIndex > -1) {
|
||||
const existingRecord = existingRecords[findIndex];
|
||||
|
||||
// Some raw value has an update_at field. We'll proceed to update only if the update_at value is different from the record's value in database
|
||||
const updateRecords = getValidRecordsForUpdate({
|
||||
tableName,
|
||||
existingRecord,
|
||||
newValue: newElement,
|
||||
});
|
||||
|
||||
return updateRaws.push(updateRecords);
|
||||
}
|
||||
|
||||
// This RawValue is not present in the database; hence, we need to create it
|
||||
return createRaws.push({record: undefined, raw: newElement});
|
||||
});
|
||||
|
||||
return {
|
||||
createRaws,
|
||||
updateRaws,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createRaws: getRawRecordPairs(rawValues),
|
||||
updateRaws,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* batchOperations: Accepts an instance of Database (either Default or Server) and an array of
|
||||
* prepareCreate/prepareUpdate 'models' and executes the actions on the database.
|
||||
* @param {BatchOperationsArgs} operation
|
||||
* @param {Database} operation.database
|
||||
* @param {Array} operation.models
|
||||
* @throws {DataOperatorException}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
batchOperations = async ({database, models}: BatchOperationsArgs) => {
|
||||
try {
|
||||
if (models.length > 0) {
|
||||
await database.action(async () => {
|
||||
await database.batch(...models);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new DataOperatorException('batchOperations error ', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareRecords: Utility method that actually calls the operators for the handlers
|
||||
* @param {PrepareRecordsArgs} prepareRecord
|
||||
* @param {Database} prepareRecord.database
|
||||
* @param {string} prepareRecord.tableName
|
||||
* @param {RawValue[]} prepareRecord.createRaws
|
||||
* @param {RawValue[]} prepareRecord.updateRaws
|
||||
* @param {(DataFactoryArgs) => Promise<Model>;} prepareRecord.recordOperator
|
||||
* @throws {DataOperatorException}
|
||||
* @returns {Promise<Model[]>}
|
||||
*/
|
||||
prepareRecords = async ({database, tableName, createRaws, updateRaws, recordOperator}: PrepareRecordsArgs) => {
|
||||
if (!database) {
|
||||
throw new DataOperatorException(
|
||||
'prepareRecords accepts only rawPosts of type RawValue[] or valid database connection',
|
||||
);
|
||||
}
|
||||
|
||||
let preparedRecords: Promise<Model>[] = [];
|
||||
|
||||
// create operation
|
||||
if (createRaws?.length) {
|
||||
const recordPromises = createRaws.map(
|
||||
(createRecord: RecordPair) => {
|
||||
return recordOperator({
|
||||
database,
|
||||
tableName,
|
||||
value: createRecord,
|
||||
action: OperationType.CREATE,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
preparedRecords = preparedRecords.concat(recordPromises);
|
||||
}
|
||||
|
||||
// update operation
|
||||
if (updateRaws?.length) {
|
||||
const recordPromises = updateRaws.map(
|
||||
(updateRecord: RecordPair) => {
|
||||
return recordOperator({
|
||||
database,
|
||||
tableName,
|
||||
value: updateRecord,
|
||||
action: OperationType.UPDATE,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
preparedRecords = preparedRecords.concat(recordPromises);
|
||||
}
|
||||
|
||||
const results = await Promise.all(preparedRecords);
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* executeInDatabase: Handles the Create/Update operations on an entity.
|
||||
* @param {PrepareForDatabaseArgs} executeInDatabase
|
||||
* @param {string} executeInDatabase.tableName
|
||||
* @param {RecordValue[]} executeInDatabase.createRaws
|
||||
* @param {RecordValue[]} executeInDatabase.updateRaws
|
||||
* @param {(DataFactoryArgs) => Promise<Model>} executeInDatabase.recordOperator
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
executeInDatabase = async ({createRaws, recordOperator, tableName, updateRaws}: PrepareForDatabaseArgs) => {
|
||||
const database = await this.getDatabase(tableName);
|
||||
|
||||
const models = await this.prepareRecords({
|
||||
database,
|
||||
tableName,
|
||||
createRaws,
|
||||
updateRaws,
|
||||
recordOperator,
|
||||
});
|
||||
|
||||
if (models?.length > 0) {
|
||||
await this.batchOperations({database, models});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getDatabase: Based on the table's name, it will return a database instance either from the 'DEFAULT' database or
|
||||
* the 'SERVER' database
|
||||
* @param {string} tableName
|
||||
* @returns {Promise<Database>}
|
||||
*/
|
||||
getDatabase = async (tableName: string) => {
|
||||
const isDefaultConnection = Object.values(MM_TABLES.DEFAULT).some((tbName) => {
|
||||
return tableName === tbName;
|
||||
});
|
||||
|
||||
const promise = isDefaultConnection ? this.getDefaultDatabase : this.getServerDatabase;
|
||||
const connection = await promise();
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
/**
|
||||
* getDefaultDatabase: Returns the default database
|
||||
* @throws {DatabaseConnectionException}
|
||||
* @returns {Promise<Database>}
|
||||
*/
|
||||
getDefaultDatabase = async () => {
|
||||
const databaseManagerClient = new DatabaseManager();
|
||||
const connection = await databaseManagerClient.getDefaultDatabase();
|
||||
if (connection === undefined) {
|
||||
throw new DatabaseConnectionException(
|
||||
'An error occurred while retrieving the default database',
|
||||
'',
|
||||
);
|
||||
}
|
||||
return connection;
|
||||
};
|
||||
|
||||
/**
|
||||
* getServerDatabase: Returns the current active server database (multi-server support)
|
||||
* @throws {DatabaseConnectionException}
|
||||
* @returns {Promise<Database>}
|
||||
*/
|
||||
getServerDatabase = async () => {
|
||||
// Third parties trying to update the database
|
||||
if (this.activeDatabase) {
|
||||
return this.activeDatabase;
|
||||
}
|
||||
|
||||
throw new DatabaseConnectionException(
|
||||
"This operator client didn't have its activeDatabase set",
|
||||
'',
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default BaseHandler;
|
||||
@@ -1,240 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {
|
||||
isRecordChannelEqualToRaw,
|
||||
isRecordChannelInfoEqualToRaw,
|
||||
isRecordMyChannelEqualToRaw,
|
||||
isRecordMyChannelSettingsEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareChannelInfoRecord,
|
||||
prepareChannelRecord,
|
||||
prepareMyChannelRecord,
|
||||
prepareMyChannelSettingsRecord,
|
||||
} from '@database/operator/prepareRecords/channel';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
});
|
||||
|
||||
it('=> HandleChannel: should write to CHANNEL entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'channel_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleChannel({
|
||||
channels: [
|
||||
{
|
||||
id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
create_at: 1600185541285,
|
||||
update_at: 1604401077256,
|
||||
delete_at: 0,
|
||||
team_id: '',
|
||||
type: 'D',
|
||||
display_name: '',
|
||||
name: 'gh781zkzkhh357b4bejephjz5u8daw__9ciscaqbrpd6d8s68k76xb9bte',
|
||||
header: '(https://mattermost',
|
||||
purpose: '',
|
||||
last_post_at: 1617311494451,
|
||||
total_msg_count: 585,
|
||||
extra_update_at: 0,
|
||||
creator_id: '',
|
||||
scheme_id: null,
|
||||
props: null,
|
||||
group_constrained: null,
|
||||
shared: null,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
rawValues: [
|
||||
{
|
||||
id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
create_at: 1600185541285,
|
||||
update_at: 1604401077256,
|
||||
delete_at: 0,
|
||||
team_id: '',
|
||||
type: 'D',
|
||||
display_name: '',
|
||||
name: 'gh781zkzkhh357b4bejephjz5u8daw__9ciscaqbrpd6d8s68k76xb9bte',
|
||||
header: '(https://mattermost',
|
||||
purpose: '',
|
||||
last_post_at: 1617311494451,
|
||||
total_msg_count: 585,
|
||||
extra_update_at: 0,
|
||||
creator_id: '',
|
||||
scheme_id: null,
|
||||
props: null,
|
||||
group_constrained: null,
|
||||
shared: null,
|
||||
},
|
||||
],
|
||||
tableName: 'Channel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelEqualToRaw,
|
||||
operator: prepareChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannelSettings: should write to MY_CHANNEL_SETTINGS entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'channel_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleMyChannelSettings({
|
||||
settings: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
rawValues: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
tableName: 'MyChannelSettings',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
operator: prepareMyChannelSettingsRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelInfo: should write to CHANNEL_INFO entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'channel_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleChannelInfo({
|
||||
channelInfos: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
member_count: 10,
|
||||
pinned_post_count: 3,
|
||||
purpose: 'sample channel ',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
rawValues: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
member_count: 10,
|
||||
pinned_post_count: 3,
|
||||
purpose: 'sample channel ',
|
||||
},
|
||||
],
|
||||
tableName: 'ChannelInfo',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
operator: prepareChannelInfoRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannel: should write to MY_CHANNEL entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'channel_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleMyChannel({
|
||||
myChannels: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
last_post_at: 1617311494451,
|
||||
last_viewed_at: 1617311494451,
|
||||
mentions_count: 3,
|
||||
message_count: 10,
|
||||
roles: 'guest',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
rawValues: [
|
||||
{
|
||||
channel_id: 'c',
|
||||
last_post_at: 1617311494451,
|
||||
last_viewed_at: 1617311494451,
|
||||
mentions_count: 3,
|
||||
message_count: 10,
|
||||
roles: 'guest',
|
||||
},
|
||||
],
|
||||
tableName: 'MyChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
operator: prepareMyChannelRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,225 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {
|
||||
isRecordGroupEqualToRaw,
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareGroupMembershipRecord,
|
||||
prepareGroupRecord,
|
||||
prepareGroupsInChannelRecord,
|
||||
prepareGroupsInTeamRecord,
|
||||
} from '@database/operator/prepareRecords/group';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** Operator: Group Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
});
|
||||
|
||||
it('=> HandleGroup: should write to GROUP entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'group_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleGroup({
|
||||
groups: [
|
||||
{
|
||||
id: 'id_groupdfjdlfkjdkfdsf',
|
||||
name: 'mobile_team',
|
||||
display_name: 'mobile team',
|
||||
description: '',
|
||||
source: '',
|
||||
remote_id: '',
|
||||
create_at: 0,
|
||||
update_at: 0,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'name',
|
||||
rawValues: [
|
||||
{
|
||||
id: 'id_groupdfjdlfkjdkfdsf',
|
||||
name: 'mobile_team',
|
||||
display_name: 'mobile team',
|
||||
description: '',
|
||||
source: '',
|
||||
remote_id: '',
|
||||
create_at: 0,
|
||||
update_at: 0,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
},
|
||||
],
|
||||
tableName: 'Group',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
operator: prepareGroupRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInTeam: should write to GROUPS_IN_TEAM entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'group_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleGroupsInTeam({
|
||||
groupsInTeams: [
|
||||
{
|
||||
team_id: 'team_899',
|
||||
team_display_name: '',
|
||||
team_type: '',
|
||||
group_id: 'group_id89',
|
||||
auto_add: true,
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
update_at: 0,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
rawValues: [
|
||||
{
|
||||
team_id: 'team_899',
|
||||
team_display_name: '',
|
||||
team_type: '',
|
||||
group_id: 'group_id89',
|
||||
auto_add: true,
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
update_at: 0,
|
||||
},
|
||||
],
|
||||
tableName: 'GroupsInTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
operator: prepareGroupsInTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInChannel: should write to GROUPS_IN_CHANNEL entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'group_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleGroupsInChannel({
|
||||
groupsInChannels: [
|
||||
{
|
||||
auto_add: true,
|
||||
channel_display_name: '',
|
||||
channel_id: 'channelid',
|
||||
channel_type: '',
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
group_id: 'groupId',
|
||||
team_display_name: '',
|
||||
team_id: '',
|
||||
team_type: '',
|
||||
update_at: 0,
|
||||
member_count: 0,
|
||||
timezone_count: 0,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
rawValues: [
|
||||
{
|
||||
auto_add: true,
|
||||
channel_display_name: '',
|
||||
channel_id: 'channelid',
|
||||
channel_type: '',
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
group_id: 'groupId',
|
||||
team_display_name: '',
|
||||
team_id: '',
|
||||
team_type: '',
|
||||
update_at: 0,
|
||||
member_count: 0,
|
||||
timezone_count: 0,
|
||||
},
|
||||
],
|
||||
tableName: 'GroupsInChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
operator: prepareGroupsInChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupMembership: should write to GROUP_MEMBERSHIP entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'group_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleGroupMembership({
|
||||
groupMemberships: [
|
||||
{
|
||||
user_id: 'u4cprpki7ri81mbx8efixcsb8jo',
|
||||
group_id: 'g4cprpki7ri81mbx8efixcsb8jo',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
rawValues: [
|
||||
{
|
||||
user_id: 'u4cprpki7ri81mbx8efixcsb8jo',
|
||||
group_id: 'g4cprpki7ri81mbx8efixcsb8jo',
|
||||
},
|
||||
],
|
||||
tableName: 'GroupMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
operator: prepareGroupMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,318 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {
|
||||
isRecordMyTeamEqualToRaw,
|
||||
isRecordSlashCommandEqualToRaw,
|
||||
isRecordTeamChannelHistoryEqualToRaw,
|
||||
isRecordTeamEqualToRaw,
|
||||
isRecordTeamMembershipEqualToRaw,
|
||||
isRecordTeamSearchHistoryEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareMyTeamRecord,
|
||||
prepareSlashCommandRecord,
|
||||
prepareTeamChannelHistoryRecord,
|
||||
prepareTeamMembershipRecord,
|
||||
prepareTeamRecord,
|
||||
prepareTeamSearchHistoryRecord,
|
||||
} from '@database/operator/prepareRecords/team';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** Operator: Team Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
});
|
||||
|
||||
it('=> HandleTeam: should write to TEAM entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleTeam({
|
||||
teams: [
|
||||
{
|
||||
id: 'rcgiyftm7jyrxnmdfdfa1osd8zswby',
|
||||
create_at: 1445538153952,
|
||||
update_at: 1588876392150,
|
||||
delete_at: 0,
|
||||
display_name: 'Contributors',
|
||||
name: 'core',
|
||||
description: '',
|
||||
email: '',
|
||||
type: 'O',
|
||||
company_name: '',
|
||||
allowed_domains: '',
|
||||
invite_id: 'codoy5s743rq5mk18i7u5dfdfksz7e',
|
||||
allow_open_invite: true,
|
||||
last_team_icon_update: 1525181587639,
|
||||
scheme_id: 'hbwgrncq1pfcdkpotzidfdmarn95o',
|
||||
group_constrained: null,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
rawValues: [
|
||||
{
|
||||
id: 'rcgiyftm7jyrxnmdfdfa1osd8zswby',
|
||||
create_at: 1445538153952,
|
||||
update_at: 1588876392150,
|
||||
delete_at: 0,
|
||||
display_name: 'Contributors',
|
||||
name: 'core',
|
||||
description: '',
|
||||
email: '',
|
||||
type: 'O',
|
||||
company_name: '',
|
||||
allowed_domains: '',
|
||||
invite_id: 'codoy5s743rq5mk18i7u5dfdfksz7e',
|
||||
allow_open_invite: true,
|
||||
last_team_icon_update: 1525181587639,
|
||||
scheme_id: 'hbwgrncq1pfcdkpotzidfdmarn95o',
|
||||
group_constrained: null,
|
||||
},
|
||||
],
|
||||
tableName: 'Team',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamEqualToRaw,
|
||||
operator: prepareTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamMemberships: should write to TEAM_MEMBERSHIP entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleTeamMemberships({
|
||||
teamMemberships: [
|
||||
{
|
||||
team_id: 'a',
|
||||
user_id: 'ab',
|
||||
roles: '3ngdqe1e7tfcbmam4qgnxp91bw',
|
||||
delete_at: 0,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
rawValues: [
|
||||
{
|
||||
team_id: 'a',
|
||||
user_id: 'ab',
|
||||
roles: '3ngdqe1e7tfcbmam4qgnxp91bw',
|
||||
delete_at: 0,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
],
|
||||
tableName: 'TeamMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamMembershipEqualToRaw,
|
||||
operator: prepareTeamMembershipRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyTeam: should write to MY_TEAM entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleMyTeam({
|
||||
myTeams: [
|
||||
{
|
||||
team_id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
rawValues: [
|
||||
{
|
||||
team_id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
},
|
||||
],
|
||||
tableName: 'MyTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
operator: prepareMyTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamChannelHistory: should write to TEAM_CHANNEL_HISTORY entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleTeamChannelHistory({
|
||||
teamChannelHistories: [
|
||||
{
|
||||
team_id: 'a',
|
||||
channel_ids: ['ca', 'cb'],
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
rawValues: [{team_id: 'a', channel_ids: ['ca', 'cb']}],
|
||||
tableName: 'TeamChannelHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
operator: prepareTeamChannelHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamSearchHistory: should write to TEAM_SEARCH_HISTORY entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleTeamSearchHistory({
|
||||
teamSearchHistories: [
|
||||
{
|
||||
team_id: 'a',
|
||||
term: 'termA',
|
||||
display_term: 'termA',
|
||||
created_at: 1445538153952,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
rawValues: [
|
||||
{
|
||||
team_id: 'a',
|
||||
term: 'termA',
|
||||
display_term: 'termA',
|
||||
created_at: 1445538153952,
|
||||
},
|
||||
],
|
||||
tableName: 'TeamSearchHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamSearchHistoryEqualToRaw,
|
||||
operator: prepareTeamSearchHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSlashCommand: should write to SLASH_COMMAND entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'team_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleSlashCommand({
|
||||
slashCommands: [
|
||||
{
|
||||
id: 'command_1',
|
||||
auto_complete: true,
|
||||
auto_complete_desc: 'mock_command',
|
||||
auto_complete_hint: 'hint',
|
||||
create_at: 1445538153952,
|
||||
creator_id: 'creator_id',
|
||||
delete_at: 1445538153952,
|
||||
description: 'description',
|
||||
display_name: 'display_name',
|
||||
icon_url: 'display_name',
|
||||
method: 'get',
|
||||
team_id: 'teamA',
|
||||
token: 'token',
|
||||
trigger: 'trigger',
|
||||
update_at: 1445538153953,
|
||||
url: 'url',
|
||||
username: 'userA',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
rawValues: [
|
||||
{
|
||||
id: 'command_1',
|
||||
auto_complete: true,
|
||||
auto_complete_desc: 'mock_command',
|
||||
auto_complete_hint: 'hint',
|
||||
create_at: 1445538153952,
|
||||
creator_id: 'creator_id',
|
||||
delete_at: 1445538153952,
|
||||
description: 'description',
|
||||
display_name: 'display_name',
|
||||
icon_url: 'display_name',
|
||||
method: 'get',
|
||||
team_id: 'teamA',
|
||||
token: 'token',
|
||||
trigger: 'trigger',
|
||||
update_at: 1445538153953,
|
||||
url: 'url',
|
||||
username: 'userA',
|
||||
},
|
||||
],
|
||||
tableName: 'SlashCommand',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordSlashCommandEqualToRaw,
|
||||
operator: prepareSlashCommandRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,352 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {
|
||||
isRecordChannelMembershipEqualToRaw,
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {
|
||||
prepareChannelMembershipRecord,
|
||||
preparePreferenceRecord,
|
||||
prepareUserRecord,
|
||||
} from '@database/operator/prepareRecords/user';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** Operator: User Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
});
|
||||
|
||||
it('=> HandleReactions: should write to both Reactions and CustomEmoji entities', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await createTestConnection({databaseName: 'user_handler', setActive: true});
|
||||
|
||||
const spyOnPrepareRecords = jest.spyOn(operatorClient as any, 'prepareRecords');
|
||||
const spyOnBatchOperation = jest.spyOn(operatorClient as any, 'batchOperations');
|
||||
|
||||
await operatorClient.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
create_at: 1608263728086,
|
||||
delete_at: 0,
|
||||
emoji_name: 'p4p1',
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
update_at: 1608263728077,
|
||||
user_id: 'ooumoqgq3bfiijzwbn8badznwc',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
// Called twice: Once for Reaction record and once for CustomEmoji record
|
||||
expect(spyOnPrepareRecords).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Only one batch operation for both entities
|
||||
expect(spyOnBatchOperation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('=> HandleUsers: should write to User entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
create_at: 1599457495881,
|
||||
update_at: 1607683720173,
|
||||
delete_at: 0,
|
||||
username: 'a.l',
|
||||
auth_service: 'saml',
|
||||
email: 'a.l@mattermost.com',
|
||||
email_verified: true,
|
||||
is_bot: false,
|
||||
nickname: '',
|
||||
first_name: 'A',
|
||||
last_name: 'L',
|
||||
position: 'Mobile Engineer',
|
||||
roles: 'system_user',
|
||||
props: {},
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
auto_responder_active: false,
|
||||
auto_responder_message: 'Hello, I am out of office and unable to respond to messages.',
|
||||
comments: 'never',
|
||||
desktop_notification_sound: 'Hello',
|
||||
push_status: 'online',
|
||||
},
|
||||
last_password_update: 1604323112537,
|
||||
last_picture_update: 1604686302260,
|
||||
locale: 'en',
|
||||
timezone: {
|
||||
automaticTimezone: 'Indian/Mauritius',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'user_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleUsers({users, prepareRecordsOnly: false});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
rawValues: [
|
||||
{
|
||||
id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
create_at: 1599457495881,
|
||||
update_at: 1607683720173,
|
||||
delete_at: 0,
|
||||
username: 'a.l',
|
||||
auth_service: 'saml',
|
||||
email: 'a.l@mattermost.com',
|
||||
email_verified: true,
|
||||
is_bot: false,
|
||||
nickname: '',
|
||||
first_name: 'A',
|
||||
last_name: 'L',
|
||||
position: 'Mobile Engineer',
|
||||
roles: 'system_user',
|
||||
props: {},
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
auto_responder_active: false,
|
||||
auto_responder_message: 'Hello, I am out of office and unable to respond to messages.',
|
||||
comments: 'never',
|
||||
desktop_notification_sound: 'Hello',
|
||||
push_status: 'online',
|
||||
},
|
||||
last_password_update: 1604323112537,
|
||||
last_picture_update: 1604686302260,
|
||||
locale: 'en',
|
||||
timezone: {
|
||||
automaticTimezone: 'Indian/Mauritius',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
tableName: 'User',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordUserEqualToRaw,
|
||||
operator: prepareUserRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandlePreferences: should write to PREFERENCE entity', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'user_handler', setActive: true});
|
||||
|
||||
await operatorClient.handlePreferences({
|
||||
preferences: [
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'group_channel_show',
|
||||
name: 'qj91hepgjfn6xr4acm5xzd8zoc',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'notifications',
|
||||
name: 'email_interval',
|
||||
value: '30',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'theme',
|
||||
name: '',
|
||||
value:
|
||||
'{"awayIndicator":"#c1b966","buttonBg":"#4cbba4","buttonColor":"#ffffff","centerChannelBg":"#2f3e4e","centerChannelColor":"#dddddd","codeTheme":"solarized-dark","dndIndicator":"#e81023","errorTextColor":"#ff6461","image":"/static/files/0b8d56c39baf992e5e4c58d74fde0fd6.png","linkColor":"#a4ffeb","mentionBg":"#b74a4a","mentionColor":"#ffffff","mentionHighlightBg":"#984063","mentionHighlightLink":"#a4ffeb","newMessageSeparator":"#5de5da","onlineIndicator":"#65dcc8","sidebarBg":"#1b2c3e","sidebarHeaderBg":"#1b2c3e","sidebarHeaderTextColor":"#ffffff","sidebarText":"#ffffff","sidebarTextActiveBorder":"#66b9a7","sidebarTextActiveColor":"#ffffff","sidebarTextHoverBg":"#4a5664","sidebarUnreadText":"#ffffff","type":"Mattermost Dark"}',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'tutorial_step',
|
||||
name: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
rawValues: [
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'group_channel_show',
|
||||
name: 'qj91hepgjfn6xr4acm5xzd8zoc',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'notifications',
|
||||
name: 'email_interval',
|
||||
value: '30',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'theme',
|
||||
name: '',
|
||||
value: '{"awayIndicator":"#c1b966","buttonBg":"#4cbba4","buttonColor":"#ffffff","centerChannelBg":"#2f3e4e","centerChannelColor":"#dddddd","codeTheme":"solarized-dark","dndIndicator":"#e81023","errorTextColor":"#ff6461","image":"/static/files/0b8d56c39baf992e5e4c58d74fde0fd6.png","linkColor":"#a4ffeb","mentionBg":"#b74a4a","mentionColor":"#ffffff","mentionHighlightBg":"#984063","mentionHighlightLink":"#a4ffeb","newMessageSeparator":"#5de5da","onlineIndicator":"#65dcc8","sidebarBg":"#1b2c3e","sidebarHeaderBg":"#1b2c3e","sidebarHeaderTextColor":"#ffffff","sidebarText":"#ffffff","sidebarTextActiveBorder":"#66b9a7","sidebarTextActiveColor":"#ffffff","sidebarTextHoverBg":"#4a5664","sidebarUnreadText":"#ffffff","type":"Mattermost Dark"}',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'tutorial_step',
|
||||
name: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
tableName: 'Preference',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordPreferenceEqualToRaw,
|
||||
operator: preparePreferenceRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelMembership: should write to CHANNEL_MEMBERSHIP entity', async () => {
|
||||
expect.assertions(2);
|
||||
const channelMemberships = [
|
||||
{
|
||||
channel_id: '17bfnb1uwb8epewp4q3x3rx9go',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'wqyby5r5pinxxdqhoaomtacdhc',
|
||||
last_viewed_at: 1613667352029,
|
||||
msg_count: 3864,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1613667352029,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
{
|
||||
channel_id: '1yw6gxfr4bn1jbyp9nr7d53yew',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'channel_user',
|
||||
last_viewed_at: 1615300540549,
|
||||
msg_count: 16,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'all',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1615300540549,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
|
||||
await createTestConnection({databaseName: 'user_handler', setActive: true});
|
||||
|
||||
await operatorClient.handleChannelMembership({
|
||||
channelMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
rawValues: [
|
||||
{
|
||||
channel_id: '17bfnb1uwb8epewp4q3x3rx9go',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'wqyby5r5pinxxdqhoaomtacdhc',
|
||||
last_viewed_at: 1613667352029,
|
||||
msg_count: 3864,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1613667352029,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
{
|
||||
channel_id: '1yw6gxfr4bn1jbyp9nr7d53yew',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'channel_user',
|
||||
last_viewed_at: 1615300540549,
|
||||
msg_count: 16,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'all',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1615300540549,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
],
|
||||
tableName: 'ChannelMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelMembershipEqualToRaw,
|
||||
operator: prepareChannelMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import BaseHandler, {BaseHandlerMix} from '@database/operator/handlers/base_handler';
|
||||
import ChannelHandler, {ChannelHandlerMix} from '@database/operator/handlers/channel';
|
||||
import GroupHandler, {GroupHandlerMix} from '@database/operator/handlers/group';
|
||||
import PostHandler, {PostHandlerMix} from '@database/operator/handlers/post';
|
||||
import TeamHandler, {TeamHandlerMix} from '@database/operator/handlers/team';
|
||||
import UserHandler, {UserHandlerMix} from '@database/operator/handlers/user';
|
||||
import {DatabaseInstance} from '@typings/database/database';
|
||||
import mix from '@utils/mix';
|
||||
|
||||
interface Operator extends BaseHandlerMix, PostHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, TeamHandlerMix {}
|
||||
|
||||
class Operator extends mix(BaseHandler).with(PostHandler, UserHandler, GroupHandler, ChannelHandler, TeamHandler) {
|
||||
database: DatabaseInstance;
|
||||
|
||||
constructor(activeDatabase?: DatabaseInstance) {
|
||||
super();
|
||||
if (activeDatabase) {
|
||||
this.activeDatabase = activeDatabase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Operator;
|
||||
@@ -1,187 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
prepareAppRecord,
|
||||
prepareCustomEmojiRecord,
|
||||
prepareGlobalRecord,
|
||||
prepareRoleRecord,
|
||||
prepareServersRecord,
|
||||
prepareSystemRecord,
|
||||
prepareTermsOfServiceRecord,
|
||||
} from '@database/operator/prepareRecords/general';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
describe('*** Isolated Prepare Records Test ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
});
|
||||
|
||||
it('=> prepareAppRecord: should return an array of type App', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareAppRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
build_number: 'build-7',
|
||||
created_at: 1,
|
||||
version_number: 'v-1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('App');
|
||||
});
|
||||
|
||||
it('=> prepareGlobalRecord: should return an array of type Global', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareGlobalRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {name: 'g-n1', value: 'g-v1'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Global');
|
||||
});
|
||||
|
||||
it('=> prepareServersRecord: should return an array of type Servers', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await databaseManagerClient.getDefaultDatabase();
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareServersRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
db_path: 'mm-server',
|
||||
display_name: 's-displayName',
|
||||
mention_count: 1,
|
||||
unread_count: 0,
|
||||
url: 'https://community.mattermost.com',
|
||||
isSecured: true,
|
||||
lastActiveAt: 1623926359,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Servers');
|
||||
});
|
||||
|
||||
it('=> prepareRoleRecord: should return an array of type Role', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareRoleRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'role-1',
|
||||
name: 'role-name-1',
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Role');
|
||||
});
|
||||
|
||||
it('=> prepareSystemRecord: should return an array of type System', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareSystemRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {id: 'system-1', name: 'system-name-1', value: 'system'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('System');
|
||||
});
|
||||
|
||||
it('=> prepareTermsOfServiceRecord: should return an array of type TermsOfService', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareTermsOfServiceRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'tos-1',
|
||||
accepted_at: 1,
|
||||
create_at: 1613667352029,
|
||||
user_id: 'user1613667352029',
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('TermsOfService');
|
||||
});
|
||||
|
||||
it('=> prepareCustomEmojiRecord: should return an array of type CustomEmoji', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareCustomEmojiRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('CustomEmoji');
|
||||
});
|
||||
});
|
||||
@@ -1,229 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import App from '@typings/database/app';
|
||||
import CustomEmoji from '@typings/database/custom_emoji';
|
||||
import {
|
||||
DataFactoryArgs,
|
||||
RawApp,
|
||||
RawCustomEmoji,
|
||||
RawGlobal,
|
||||
RawRole,
|
||||
RawServers,
|
||||
RawSystem,
|
||||
RawTermsOfService,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import Global from '@typings/database/global';
|
||||
import Role from '@typings/database/role';
|
||||
import Servers from '@typings/database/servers';
|
||||
import System from '@typings/database/system';
|
||||
import TermsOfService from '@typings/database/terms_of_service';
|
||||
|
||||
const {APP, GLOBAL, SERVERS} = MM_TABLES.DEFAULT;
|
||||
const {
|
||||
CUSTOM_EMOJI,
|
||||
ROLE,
|
||||
SYSTEM,
|
||||
TERMS_OF_SERVICE,
|
||||
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* prepareAppRecord: Prepares record of entity 'App' from the DEFAULT database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareAppRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawApp;
|
||||
const record = value.record as App;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (app: App) => {
|
||||
app._raw.id = isCreateAction ? app.id : record.id;
|
||||
app.buildNumber = raw?.build_number;
|
||||
app.createdAt = raw?.created_at;
|
||||
app.versionNumber = raw?.version_number;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
generator,
|
||||
tableName: APP,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareGlobalRecord: Prepares record of entity 'Global' from the DEFAULT database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareGlobalRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawGlobal;
|
||||
const record = value.record as Global;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (global: Global) => {
|
||||
global._raw.id = isCreateAction ? global.id : record.id;
|
||||
global.name = raw?.name;
|
||||
global.value = raw?.value;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
generator,
|
||||
tableName: GLOBAL,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareServersRecord: Prepares record of entity 'Servers' from the DEFAULT database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareServersRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawServers;
|
||||
const record = value.record as Servers;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (servers: Servers) => {
|
||||
servers._raw.id = isCreateAction ? servers.id : record.id;
|
||||
servers.dbPath = raw?.db_path;
|
||||
servers.displayName = raw?.display_name;
|
||||
servers.mentionCount = raw?.mention_count;
|
||||
servers.unreadCount = raw?.unread_count;
|
||||
servers.url = raw?.url;
|
||||
servers.isSecured = raw?.isSecured;
|
||||
servers.lastActiveAt = raw?.lastActiveAt;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: SERVERS,
|
||||
value,
|
||||
generator,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareCustomEmojiRecord: Prepares record of entity 'CustomEmoji' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareCustomEmojiRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawCustomEmoji;
|
||||
const record = value.record as CustomEmoji;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (emoji: CustomEmoji) => {
|
||||
emoji._raw.id = isCreateAction ? (raw?.id ?? emoji.id) : record.id;
|
||||
emoji.name = raw.name;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: CUSTOM_EMOJI,
|
||||
value,
|
||||
generator,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareRoleRecord: Prepares record of entity 'Role' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareRoleRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawRole;
|
||||
const record = value.record as Role;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (role: Role) => {
|
||||
role._raw.id = isCreateAction ? (raw?.id ?? role.id) : record.id;
|
||||
role.name = raw?.name;
|
||||
role.permissions = raw?.permissions;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: ROLE,
|
||||
value,
|
||||
generator,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareSystemRecord: Prepares record of entity 'System' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareSystemRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawSystem;
|
||||
const record = value.record as System;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (system: System) => {
|
||||
system._raw.id = isCreateAction ? (raw?.id ?? system.id) : record.id;
|
||||
system.name = raw?.name;
|
||||
system.value = raw?.value;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: SYSTEM,
|
||||
value,
|
||||
generator,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareTermsOfServiceRecord: Prepares record of entity 'TermsOfService' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareTermsOfServiceRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
const raw = value.raw as RawTermsOfService;
|
||||
const record = value.record as TermsOfService;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (tos: TermsOfService) => {
|
||||
tos._raw.id = isCreateAction ? (raw?.id ?? tos.id) : record.id;
|
||||
tos.acceptedAt = raw?.accepted_at;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: TERMS_OF_SERVICE,
|
||||
value,
|
||||
generator,
|
||||
});
|
||||
};
|
||||
@@ -1,19 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import App from '@typings/database/app';
|
||||
import Channel from '@typings/database/channel';
|
||||
import ChannelInfo from '@typings/database/channel_info';
|
||||
import ChannelMembership from '@typings/database/channel_membership';
|
||||
import CustomEmoji from '@typings/database/custom_emoji';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import ChannelInfo from '@typings/database/models/servers/channel_info';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import CustomEmoji from '@typings/database/models/servers/custom_emoji';
|
||||
import {
|
||||
RawApp,
|
||||
RawChannel,
|
||||
RawChannelInfo,
|
||||
RawChannelMembership,
|
||||
RawCustomEmoji,
|
||||
RawDraft,
|
||||
RawGlobal,
|
||||
RawGroup,
|
||||
RawGroupMembership,
|
||||
RawGroupsInChannel,
|
||||
@@ -24,7 +21,6 @@ import {
|
||||
RawPost,
|
||||
RawPreference,
|
||||
RawRole,
|
||||
RawServers,
|
||||
RawSlashCommand,
|
||||
RawSystem,
|
||||
RawTeam,
|
||||
@@ -34,27 +30,25 @@ import {
|
||||
RawTermsOfService,
|
||||
RawUser,
|
||||
} from '@typings/database/database';
|
||||
import Draft from '@typings/database/draft';
|
||||
import Global from '@typings/database/global';
|
||||
import Group from '@typings/database/group';
|
||||
import GroupMembership from '@typings/database/group_membership';
|
||||
import GroupsInChannel from '@typings/database/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/groups_in_team';
|
||||
import MyChannel from '@typings/database/my_channel';
|
||||
import MyChannelSettings from '@typings/database/my_channel_settings';
|
||||
import MyTeam from '@typings/database/my_team';
|
||||
import Post from '@typings/database/post';
|
||||
import Preference from '@typings/database/preference';
|
||||
import Role from '@typings/database/role';
|
||||
import Servers from '@typings/database/servers';
|
||||
import SlashCommand from '@typings/database/slash_command';
|
||||
import System from '@typings/database/system';
|
||||
import Team from '@typings/database/team';
|
||||
import TeamChannelHistory from '@typings/database/team_channel_history';
|
||||
import TeamMembership from '@typings/database/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/team_search_history';
|
||||
import TermsOfService from '@typings/database/terms_of_service';
|
||||
import User from '@typings/database/user';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
import MyChannel from '@typings/database/models/servers/my_channel';
|
||||
import MyChannelSettings from '@typings/database/models/servers/my_channel_settings';
|
||||
import MyTeam from '@typings/database/models/servers/my_team';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import Preference from '@typings/database/models/servers/preference';
|
||||
import Role from '@typings/database/models/servers/role';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import System from '@typings/database/models/servers/system';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import TeamChannelHistory from '@typings/database/models/servers/team_channel_history';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/models/servers/team_search_history';
|
||||
import TermsOfService from '@typings/database/models/servers/terms_of_service';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
/**
|
||||
* This file contains all the comparators that are used by the handlers to find out which records to truly update and
|
||||
@@ -63,18 +57,6 @@ import User from '@typings/database/user';
|
||||
* 'record' and the 'raw'
|
||||
*/
|
||||
|
||||
export const isRecordAppEqualToRaw = (record: App, raw: RawApp) => {
|
||||
return (raw.build_number === record.buildNumber && raw.version_number === record.versionNumber);
|
||||
};
|
||||
|
||||
export const isRecordGlobalEqualToRaw = (record: Global, raw: RawGlobal) => {
|
||||
return raw.name === record.name && raw.value === record.value;
|
||||
};
|
||||
|
||||
export const isRecordServerEqualToRaw = (record: Servers, raw: RawServers) => {
|
||||
return raw.url === record.url && raw.db_path === record.dbPath;
|
||||
};
|
||||
|
||||
export const isRecordRoleEqualToRaw = (record: Role, raw: RawRole) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordChannelEqualToRaw,
|
||||
isRecordChannelInfoEqualToRaw,
|
||||
isRecordMyChannelEqualToRaw,
|
||||
isRecordMyChannelSettingsEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformChannelInfoRecord,
|
||||
transformChannelRecord,
|
||||
transformMyChannelRecord,
|
||||
transformMyChannelSettingsRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/channel';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
import type {RawChannel} from '@typings/database/database';
|
||||
|
||||
describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleChannel: should write to the CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const channels: RawChannel[] = [
|
||||
{
|
||||
id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
create_at: 1600185541285,
|
||||
update_at: 1604401077256,
|
||||
delete_at: 0,
|
||||
team_id: '',
|
||||
type: 'D',
|
||||
display_name: '',
|
||||
name: 'gh781zkzkhh357b4bejephjz5u8daw__9ciscaqbrpd6d8s68k76xb9bte',
|
||||
header: '(https://mattermost',
|
||||
purpose: '',
|
||||
last_post_at: 1617311494451,
|
||||
total_msg_count: 585,
|
||||
extra_update_at: 0,
|
||||
creator_id: '',
|
||||
group_constrained: null,
|
||||
shared: false,
|
||||
props: null,
|
||||
scheme_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleChannel({
|
||||
channels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: channels,
|
||||
tableName: 'Channel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelEqualToRaw,
|
||||
transformer: transformChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannelSettings: should write to the MY_CHANNEL_SETTINGS table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const settings = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyChannelSettings({
|
||||
settings,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: settings,
|
||||
tableName: 'MyChannelSettings',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
transformer: transformMyChannelSettingsRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelInfo: should write to the CHANNEL_INFO table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator as any, 'handleRecords');
|
||||
const channelInfos = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
member_count: 10,
|
||||
pinned_post_count: 3,
|
||||
purpose: 'sample channel ',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleChannelInfo({
|
||||
channelInfos,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: channelInfos,
|
||||
tableName: 'ChannelInfo',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
transformer: transformChannelInfoRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannel: should write to the MY_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myChannels = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
last_post_at: 1617311494451,
|
||||
last_viewed_at: 1617311494451,
|
||||
mentions_count: 3,
|
||||
message_count: 10,
|
||||
roles: 'guest',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyChannel({
|
||||
myChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: myChannels,
|
||||
tableName: 'MyChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
transformer: transformMyChannelRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,24 +8,24 @@ import {
|
||||
isRecordChannelInfoEqualToRaw,
|
||||
isRecordMyChannelEqualToRaw,
|
||||
isRecordMyChannelSettingsEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
prepareChannelInfoRecord,
|
||||
prepareChannelRecord,
|
||||
prepareMyChannelRecord,
|
||||
prepareMyChannelSettingsRecord,
|
||||
} from '@database/operator/prepareRecords/channel';
|
||||
transformChannelInfoRecord,
|
||||
transformChannelRecord,
|
||||
transformMyChannelRecord,
|
||||
transformMyChannelSettingsRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/channel';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import Channel from '@typings/database/channel';
|
||||
import ChannelInfo from '@typings/database/channel_info';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import ChannelInfo from '@typings/database/models/servers/channel_info';
|
||||
import {
|
||||
HandleChannelArgs,
|
||||
HandleChannelInfoArgs,
|
||||
HandleMyChannelArgs,
|
||||
HandleMyChannelSettingsArgs,
|
||||
} from '@typings/database/database';
|
||||
import MyChannel from '@typings/database/my_channel';
|
||||
import MyChannelSettings from '@typings/database/my_channel_settings';
|
||||
import MyChannel from '@typings/database/models/servers/my_channel';
|
||||
import MyChannelSettings from '@typings/database/models/servers/my_channel_settings';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
@@ -43,7 +43,7 @@ export interface ChannelHandlerMix {
|
||||
|
||||
const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleChannel: Handler responsible for the Create/Update operations occurring on the CHANNEL entity from the 'Server' schema
|
||||
* handleChannel: Handler responsible for the Create/Update operations occurring on the CHANNEL table from the 'Server' schema
|
||||
* @param {HandleChannelArgs} channelsArgs
|
||||
* @param {RawChannel[]} channelsArgs.channels
|
||||
* @param {boolean} channelsArgs.prepareRecordsOnly
|
||||
@@ -59,14 +59,14 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: channels, key: 'id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: channels, key: 'id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordChannelEqualToRaw,
|
||||
operator: prepareChannelRecord,
|
||||
transformer: transformChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL,
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyChannelSettings: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL_SETTINGS entity from the 'Server' schema
|
||||
* handleMyChannelSettings: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL_SETTINGS table from the 'Server' schema
|
||||
* @param {HandleMyChannelSettingsArgs} settingsArgs
|
||||
* @param {RawMyChannelSettings[]} settingsArgs.settings
|
||||
* @param {boolean} settingsArgs.prepareRecordsOnly
|
||||
@@ -90,14 +90,14 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: settings, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: settings, key: 'channel_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
operator: prepareMyChannelSettingsRecord,
|
||||
transformer: transformMyChannelSettingsRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_CHANNEL_SETTINGS,
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleChannelInfo: Handler responsible for the Create/Update operations occurring on the CHANNEL_INFO entity from the 'Server' schema
|
||||
* handleChannelInfo: Handler responsible for the Create/Update operations occurring on the CHANNEL_INFO table from the 'Server' schema
|
||||
* @param {HandleChannelInfoArgs} channelInfosArgs
|
||||
* @param {RawChannelInfo[]} channelInfosArgs.channelInfos
|
||||
* @param {boolean} channelInfosArgs.prepareRecordsOnly
|
||||
@@ -121,17 +121,17 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: channelInfos,
|
||||
key: 'channel_id',
|
||||
});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
operator: prepareChannelInfoRecord,
|
||||
transformer: transformChannelInfoRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL_INFO,
|
||||
});
|
||||
|
||||
@@ -139,7 +139,7 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyChannel: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL entity from the 'Server' schema
|
||||
* handleMyChannel: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleMyChannelArgs} myChannelsArgs
|
||||
* @param {RawMyChannel[]} myChannelsArgs.myChannels
|
||||
* @param {boolean} myChannelsArgs.prepareRecordsOnly
|
||||
@@ -155,17 +155,17 @@ const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: myChannels,
|
||||
key: 'channel_id',
|
||||
});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
operator: prepareMyChannelRecord,
|
||||
transformer: transformMyChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_CHANNEL,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordGroupEqualToRaw,
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Group Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleGroup: should write to the GROUP table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groups = [
|
||||
{
|
||||
id: 'id_groupdfjdlfkjdkfdsf',
|
||||
name: 'mobile_team',
|
||||
display_name: 'mobile team',
|
||||
description: '',
|
||||
source: '',
|
||||
remote_id: '',
|
||||
create_at: 0,
|
||||
update_at: 0,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroup({
|
||||
groups,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'name',
|
||||
createOrUpdateRawValues: groups,
|
||||
tableName: 'Group',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
transformer: transformGroupRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInTeam: should write to the GROUPS_IN_TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInTeams = [
|
||||
{
|
||||
team_id: 'team_899',
|
||||
team_display_name: '',
|
||||
team_type: '',
|
||||
group_id: 'group_id89',
|
||||
auto_add: true,
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
update_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupsInTeam({
|
||||
groupsInTeams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInTeams,
|
||||
tableName: 'GroupsInTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInChannel: should write to the GROUPS_IN_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInChannels = [
|
||||
{
|
||||
auto_add: true,
|
||||
channel_display_name: '',
|
||||
channel_id: 'channelid',
|
||||
channel_type: '',
|
||||
create_at: 0,
|
||||
delete_at: 0,
|
||||
group_id: 'groupId',
|
||||
team_display_name: '',
|
||||
team_id: '',
|
||||
team_type: '',
|
||||
update_at: 0,
|
||||
member_count: 0,
|
||||
timezone_count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupsInChannel({
|
||||
groupsInChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInChannels,
|
||||
tableName: 'GroupsInChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupMembership: should write to the GROUP_MEMBERSHIP table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupMemberships = [
|
||||
{
|
||||
user_id: 'u4cprpki7ri81mbx8efixcsb8jo',
|
||||
group_id: 'g4cprpki7ri81mbx8efixcsb8jo',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupMembership({
|
||||
groupMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: groupMemberships,
|
||||
tableName: 'GroupMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
transformer: transformGroupMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
prepareGroupMembershipRecord,
|
||||
prepareGroupRecord,
|
||||
prepareGroupsInChannelRecord,
|
||||
prepareGroupsInTeamRecord,
|
||||
} from '@database/operator/prepareRecords/group';
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {
|
||||
HandleGroupArgs,
|
||||
@@ -22,10 +22,10 @@ import {
|
||||
HandleGroupsInChannelArgs,
|
||||
HandleGroupsInTeamArgs,
|
||||
} from '@typings/database/database';
|
||||
import Group from '@typings/database/group';
|
||||
import GroupMembership from '@typings/database/group_membership';
|
||||
import GroupsInChannel from '@typings/database/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/groups_in_team';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
|
||||
const {
|
||||
GROUP,
|
||||
@@ -43,7 +43,7 @@ export interface GroupHandlerMix {
|
||||
|
||||
const GroupHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleGroupMembership: Handler responsible for the Create/Update operations occurring on the GROUP_MEMBERSHIP entity from the 'Server' schema
|
||||
* handleGroupMembership: Handler responsible for the Create/Update operations occurring on the GROUP_MEMBERSHIP table from the 'Server' schema
|
||||
* @param {HandleGroupMembershipArgs} groupMembershipsArgs
|
||||
* @param {RawGroupMembership[]} groupMembershipsArgs.groupMemberships
|
||||
* @param {boolean} groupMembershipsArgs.prepareRecordsOnly
|
||||
@@ -59,14 +59,14 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: groupMemberships, key: 'group_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupMemberships, key: 'group_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
operator: prepareGroupMembershipRecord,
|
||||
transformer: transformGroupMembershipRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUP_MEMBERSHIP,
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroup: Handler responsible for the Create/Update operations occurring on the GROUP entity from the 'Server' schema
|
||||
* handleGroup: Handler responsible for the Create/Update operations occurring on the GROUP table from the 'Server' schema
|
||||
* @param {HandleGroupArgs} groupsArgs
|
||||
* @param {RawGroup[]} groupsArgs.groups
|
||||
* @param {boolean} groupsArgs.prepareRecordsOnly
|
||||
@@ -90,14 +90,14 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: groups, key: 'name'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'name'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
operator: prepareGroupRecord,
|
||||
transformer: transformGroupRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUP,
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_TEAM entity from the 'Server' schema
|
||||
* handleGroupsInTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_TEAM table from the 'Server' schema
|
||||
* @param {HandleGroupsInTeamArgs} groupsInTeamsArgs
|
||||
* @param {RawGroupsInTeam[]} groupsInTeamsArgs.groupsInTeams
|
||||
* @param {boolean} groupsInTeamsArgs.prepareRecordsOnly
|
||||
@@ -121,14 +121,14 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: groupsInTeams, key: 'group_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInTeams, key: 'group_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
operator: prepareGroupsInTeamRecord,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_TEAM,
|
||||
});
|
||||
|
||||
@@ -136,7 +136,7 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_CHANNEL entity from the 'Server' schema
|
||||
* handleGroupsInChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleGroupsInChannelArgs} groupsInChannelsArgs
|
||||
* @param {RawGroupsInChannel[]} groupsInChannelsArgs.groupsInChannels
|
||||
* @param {boolean} groupsInChannelsArgs.prepareRecordsOnly
|
||||
@@ -152,14 +152,14 @@ const GroupHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: groupsInChannels, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInChannels, key: 'channel_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
operator: prepareGroupsInChannelRecord,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_CHANNEL,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCustomEmojiRecord,
|
||||
transformRoleRecord,
|
||||
transformSystemRecord,
|
||||
transformTermsOfServiceRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {RawRole, RawTermsOfService} from '@typings/database/database';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** DataOperator: Base Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleRole: should write to the ROLE table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const roles: RawRole[] = [
|
||||
{
|
||||
id: 'custom-role-id-1',
|
||||
name: 'custom-role-1',
|
||||
permissions: ['custom-permission-1'],
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleRole({
|
||||
roles,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
transformer: transformRoleRecord,
|
||||
findMatchingRecordBy: isRecordRoleEqualToRaw,
|
||||
createOrUpdateRawValues: roles,
|
||||
tableName: 'Role',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleCustomEmojis: should write to the CUSTOM_EMOJI table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const emojis = [
|
||||
{
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleCustomEmojis({
|
||||
emojis,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: emojis,
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSystem: should write to the SYSTEM table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const systems = [{name: 'system-1', value: 'system-1'}];
|
||||
|
||||
await operator.handleSystem({
|
||||
systems,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordSystemEqualToRaw,
|
||||
fieldName: 'name',
|
||||
transformer: transformSystemRecord,
|
||||
createOrUpdateRawValues: systems,
|
||||
tableName: 'System',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTermsOfService: should write to the TERMS_OF_SERVICE table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const termOfService: RawTermsOfService[] = [
|
||||
{
|
||||
id: 'tos-1',
|
||||
accepted_at: 1,
|
||||
create_at: 1613667352029,
|
||||
user_id: 'user1613667352029',
|
||||
text: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTermOfService({
|
||||
termOfService,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordTermsOfServiceEqualToRaw,
|
||||
fieldName: 'id',
|
||||
transformer: transformTermsOfServiceRecord,
|
||||
createOrUpdateRawValues: termOfService,
|
||||
tableName: 'TermsOfService',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> No table name: should not call execute if tableName is invalid', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
const appOperator = DatabaseManager.appDatabase?.operator;
|
||||
expect(appDatabase).toBeTruthy();
|
||||
expect(appOperator).toBeTruthy();
|
||||
|
||||
await expect(
|
||||
operator?.handleRecords({
|
||||
fieldName: 'invalidField',
|
||||
tableName: 'INVALID_TABLE_NAME',
|
||||
|
||||
// @ts-expect-error: Type does not match RawValue
|
||||
createOrUpdateRawValues: [{id: 'tos-1', accepted_at: 1}],
|
||||
}),
|
||||
).rejects.toThrow(DataOperatorException);
|
||||
});
|
||||
});
|
||||
122
app/database/operator/server_data_operator/handlers/index.ts
Normal file
122
app/database/operator/server_data_operator/handlers/index.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import BaseDataOperator from '@database/operator/base_data_operator';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCustomEmojiRecord,
|
||||
transformRoleRecord,
|
||||
transformSystemRecord,
|
||||
transformTermsOfServiceRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {HandleCustomEmojiArgs, HandleRoleArgs, HandleSystemArgs, HandleTOSArgs, OperationArgs} from '@typings/database/database';
|
||||
|
||||
const {SERVER: {CUSTOM_EMOJI, ROLE, SYSTEM, TERMS_OF_SERVICE}} = MM_TABLES;
|
||||
|
||||
export default class ServerDataOperatorBase extends BaseDataOperator {
|
||||
handleRole = async ({roles, prepareRecordsOnly = true}: HandleRoleArgs) => {
|
||||
if (!roles.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleRole',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordRoleEqualToRaw,
|
||||
transformer: transformRoleRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: roles, key: 'id'}),
|
||||
tableName: ROLE,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleCustomEmojis = async ({emojis, prepareRecordsOnly = true}: HandleCustomEmojiArgs) => {
|
||||
if (!emojis.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleCustomEmojis',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: emojis, key: 'id'}),
|
||||
tableName: CUSTOM_EMOJI,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleSystem = async ({systems, prepareRecordsOnly = true}: HandleSystemArgs) => {
|
||||
if (!systems.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleSystem',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordSystemEqualToRaw,
|
||||
transformer: transformSystemRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: systems, key: 'name'}),
|
||||
tableName: SYSTEM,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleTermOfService = async ({termOfService, prepareRecordsOnly = true}: HandleTOSArgs) => {
|
||||
if (!termOfService.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleTermOfService',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordTermsOfServiceEqualToRaw,
|
||||
transformer: transformTermsOfServiceRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: termOfService, key: 'id'}),
|
||||
tableName: TERMS_OF_SERVICE,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute: Handles the Create/Update operations on an table.
|
||||
* @param {OperationArgs} execute
|
||||
* @param {string} execute.tableName
|
||||
* @param {RecordValue[]} execute.createRaws
|
||||
* @param {RecordValue[]} execute.updateRaws
|
||||
* @param {(TransformerArgs) => Promise<Model>} execute.recordOperator
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
execute = async ({createRaws, transformer, tableName, updateRaws}: OperationArgs) => {
|
||||
const models = await this.prepareRecords({
|
||||
tableName,
|
||||
createRaws,
|
||||
updateRaws,
|
||||
transformer,
|
||||
});
|
||||
|
||||
if (models?.length > 0) {
|
||||
await this.batchRecords(models);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,42 +2,24 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import Operator from '@database/operator';
|
||||
import {isRecordDraftEqualToRaw} from '@database/operator/comparators';
|
||||
import {prepareDraftRecord} from '@database/operator/prepareRecords/post';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import {isRecordDraftEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformDraftRecord} from '@database/operator/server_data_operator/transformers/post';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Post Handlers tests ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
let operatorClient: Operator;
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'base_handler',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'baseHandler.test.com',
|
||||
},
|
||||
});
|
||||
|
||||
operatorClient = new Operator(database!);
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleDraft: should write to the Draft entity', async () => {
|
||||
it('=> HandleDraft: should write to the the Draft table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await createTestConnection({databaseName: 'post_handler', setActive: true});
|
||||
|
||||
const spyOnHandleEntityRecords = jest.spyOn(operatorClient as any, 'handleEntityRecords');
|
||||
const values = [
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const drafts = [
|
||||
{
|
||||
channel_id: '4r9jmr7eqt8dxq3f9woypzurrychannelid',
|
||||
files: [
|
||||
@@ -63,19 +45,19 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
},
|
||||
];
|
||||
|
||||
await operatorClient.handleDraft({drafts: values, prepareRecordsOnly: false});
|
||||
await operator.handleDraft({drafts, prepareRecordsOnly: false});
|
||||
|
||||
expect(spyOnHandleEntityRecords).toHaveBeenCalledWith({
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordDraftEqualToRaw,
|
||||
fieldName: 'channel_id',
|
||||
operator: prepareDraftRecord,
|
||||
rawValues: values,
|
||||
transformer: transformDraftRecord,
|
||||
createOrUpdateRawValues: drafts,
|
||||
tableName: 'Draft',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandlePosts: should write to Post and its sub-child entities', async () => {
|
||||
it('=> HandlePosts: should write to the Post and its sub-child tables', async () => {
|
||||
expect.assertions(12);
|
||||
|
||||
const posts = [
|
||||
@@ -227,17 +209,15 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleFiles = jest.spyOn(operatorClient as any, 'handleFiles');
|
||||
const spyOnHandlePostMetadata = jest.spyOn(operatorClient as any, 'handlePostMetadata');
|
||||
const spyOnHandleReactions = jest.spyOn(operatorClient as any, 'handleReactions');
|
||||
const spyOnHandleCustomEmojis = jest.spyOn(operatorClient as any, 'handleIsolatedEntity');
|
||||
const spyOnHandlePostsInThread = jest.spyOn(operatorClient as any, 'handlePostsInThread');
|
||||
const spyOnHandlePostsInChannel = jest.spyOn(operatorClient as any, 'handlePostsInChannel');
|
||||
|
||||
await createTestConnection({databaseName: 'post_handler', setActive: true});
|
||||
const spyOnHandleFiles = jest.spyOn(operator, 'handleFiles');
|
||||
const spyOnHandlePostMetadata = jest.spyOn(operator, 'handlePostMetadata');
|
||||
const spyOnHandleReactions = jest.spyOn(operator, 'handleReactions');
|
||||
const spyOnHandleCustomEmojis = jest.spyOn(operator, 'handleCustomEmojis');
|
||||
const spyOnHandlePostsInThread = jest.spyOn(operator, 'handlePostsInThread');
|
||||
const spyOnHandlePostsInChannel = jest.spyOn(operator, 'handlePostsInChannel');
|
||||
|
||||
// handlePosts will in turn call handlePostsInThread
|
||||
await operatorClient.handlePosts({
|
||||
await operator.handlePosts({
|
||||
orders: [
|
||||
'8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
'8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
@@ -338,9 +318,8 @@ describe('*** Operator: Post Handlers tests ***', () => {
|
||||
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledWith({
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
values: [
|
||||
emojis: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
create_at: 1502389307432,
|
||||
@@ -6,15 +6,15 @@ import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {isRecordDraftEqualToRaw, isRecordPostEqualToRaw} from '@database/operator/comparators';
|
||||
import {isRecordDraftEqualToRaw, isRecordPostEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
prepareDraftRecord,
|
||||
prepareFileRecord,
|
||||
preparePostInThreadRecord,
|
||||
preparePostMetadataRecord,
|
||||
preparePostRecord,
|
||||
preparePostsInChannelRecord,
|
||||
} from '@database/operator/prepareRecords/post';
|
||||
transformDraftRecord,
|
||||
transformFileRecord,
|
||||
transformPostInThreadRecord,
|
||||
transformPostMetadataRecord,
|
||||
transformPostRecord,
|
||||
transformPostsInChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {getRawRecordPairs, getUniqueRawsBy, retrieveRecords} from '@database/operator/utils/general';
|
||||
import {createPostsChain, sanitizePosts} from '@database/operator/utils/post';
|
||||
import {
|
||||
@@ -31,14 +31,13 @@ import {
|
||||
RawPostsInThread,
|
||||
RawReaction, RecordPair,
|
||||
} from '@typings/database/database';
|
||||
import Draft from '@typings/database/draft';
|
||||
import {IsolatedEntities} from '@typings/database/enums';
|
||||
import File from '@typings/database/file';
|
||||
import Post from '@typings/database/post';
|
||||
import PostMetadata from '@typings/database/post_metadata';
|
||||
import PostsInChannel from '@typings/database/posts_in_channel';
|
||||
import PostsInThread from '@typings/database/posts_in_thread';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import File from '@typings/database/models/servers/file';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import PostMetadata from '@typings/database/models/servers/post_metadata';
|
||||
import PostsInChannel from '@typings/database/models/servers/posts_in_channel';
|
||||
import PostsInThread from '@typings/database/models/servers/posts_in_thread';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
|
||||
const {
|
||||
DRAFT,
|
||||
@@ -60,7 +59,7 @@ export interface PostHandlerMix {
|
||||
|
||||
const PostHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleDraft: Handler responsible for the Create/Update operations occurring the Draft entity from the 'Server' schema
|
||||
* handleDraft: Handler responsible for the Create/Update operations occurring the Draft table from the 'Server' schema
|
||||
* @param {HandleDraftArgs} draftsArgs
|
||||
* @param {RawDraft[]} draftsArgs.drafts
|
||||
* @param {boolean} draftsArgs.prepareRecordsOnly
|
||||
@@ -72,18 +71,18 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
if (!drafts.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "drafts" array has been passed to the handleReactions method',
|
||||
'An empty "drafts" array has been passed to the handleDraft method',
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: drafts, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: drafts, key: 'channel_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordDraftEqualToRaw,
|
||||
operator: prepareDraftRecord,
|
||||
transformer: transformDraftRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: DRAFT,
|
||||
});
|
||||
|
||||
@@ -91,7 +90,7 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePosts: Handler responsible for the Create/Update operations occurring on the Post entity from the 'Server' schema
|
||||
* handlePosts: Handler responsible for the Create/Update operations occurring on the Post table from the 'Server' schema
|
||||
* @param {HandlePostsArgs} handlePosts
|
||||
* @param {string[]} handlePosts.orders
|
||||
* @param {RawPost[]} handlePosts.values
|
||||
@@ -120,8 +119,8 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
});
|
||||
|
||||
// Here we verify in our database that the postsOrdered truly need 'CREATION'
|
||||
const futureEntries = await this.processInputs({
|
||||
rawValues: postsOrdered,
|
||||
const futureEntries = await this.processRecords({
|
||||
createOrUpdateRawValues: postsOrdered,
|
||||
tableName,
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
@@ -143,20 +142,17 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
rawPosts: postsOrdered,
|
||||
});
|
||||
|
||||
const database = await this.getDatabase(tableName);
|
||||
|
||||
// Prepares records for batch processing onto the 'Post' entity for the server schema
|
||||
// Prepares records for batch processing onto the 'Post' table for the server schema
|
||||
const posts = (await this.prepareRecords({
|
||||
createRaws: linkedRawPosts,
|
||||
database,
|
||||
recordOperator: preparePostRecord,
|
||||
transformer: transformPostRecord,
|
||||
tableName,
|
||||
})) as Post[];
|
||||
|
||||
// Appends the processed records into the final batch array
|
||||
batch = batch.concat(posts);
|
||||
|
||||
// Starts extracting information from each post to build up for related entities' data
|
||||
// Starts extracting information from each post to build up for related tables' data
|
||||
for (const post of postsOrdered) {
|
||||
// PostInThread handler: checks for id === root_id , if so, then call PostsInThread operator
|
||||
if (!post.root_id) {
|
||||
@@ -213,14 +209,13 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
}
|
||||
|
||||
if (batch.length) {
|
||||
await this.batchOperations({database, models: batch});
|
||||
await this.batchRecords(batch);
|
||||
}
|
||||
|
||||
// LAST: calls handler for CustomEmojis, PostsInThread, PostsInChannel
|
||||
if (emojis.length) {
|
||||
await this.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.CUSTOM_EMOJI,
|
||||
values: emojis,
|
||||
await this.handleCustomEmojis({
|
||||
emojis,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
@@ -236,11 +231,11 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
if (postsUnordered.length) {
|
||||
// Truly update those posts that have a different update_at value
|
||||
await this.handleEntityRecords({
|
||||
await this.handleRecords({
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
operator: preparePostRecord,
|
||||
rawValues: postsUnordered,
|
||||
trasformer: transformPostRecord,
|
||||
createOrUpdateRawValues: postsUnordered,
|
||||
tableName: POST,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
@@ -248,7 +243,7 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleFiles: Handler responsible for the Create/Update operations occurring on the File entity from the 'Server' schema
|
||||
* handleFiles: Handler responsible for the Create/Update operations occurring on the File table from the 'Server' schema
|
||||
* @param {HandleFilesArgs} handleFiles
|
||||
* @param {RawFile[]} handleFiles.files
|
||||
* @param {boolean} handleFiles.prepareRecordsOnly
|
||||
@@ -259,12 +254,9 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
return [];
|
||||
}
|
||||
|
||||
const database = await this.getDatabase(FILE);
|
||||
|
||||
const postFiles = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(files),
|
||||
database,
|
||||
recordOperator: prepareFileRecord,
|
||||
transformer: transformFileRecord,
|
||||
tableName: FILE,
|
||||
});
|
||||
|
||||
@@ -273,14 +265,14 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
}
|
||||
|
||||
if (postFiles?.length) {
|
||||
await this.batchOperations({database, models: [...postFiles]});
|
||||
await this.batchRecords(postFiles);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostMetadata: Handler responsible for the Create/Update operations occurring on the PostMetadata entity from the 'Server' schema
|
||||
* handlePostMetadata: Handler responsible for the Create/Update operations occurring on the PostMetadata table from the 'Server' schema
|
||||
* @param {HandlePostMetadataArgs} handlePostMetadata
|
||||
* @param {{embed: RawEmbed[], postId: string}[] | undefined} handlePostMetadata.embeds
|
||||
* @param {{images: Dictionary<PostImage>, postId: string}[] | undefined} handlePostMetadata.images
|
||||
@@ -317,12 +309,9 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
return [];
|
||||
}
|
||||
|
||||
const database = await this.getDatabase(POST_METADATA);
|
||||
|
||||
const postMetas = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(metadata),
|
||||
database,
|
||||
recordOperator: preparePostMetadataRecord,
|
||||
transformer: transformPostMetadataRecord,
|
||||
tableName: POST_METADATA,
|
||||
});
|
||||
|
||||
@@ -331,14 +320,14 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
}
|
||||
|
||||
if (postMetas?.length) {
|
||||
await this.batchOperations({database, models: [...postMetas]});
|
||||
await this.batchRecords(postMetas);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostsInThread: Handler responsible for the Create/Update operations occurring on the PostsInThread entity from the 'Server' schema
|
||||
* handlePostsInThread: Handler responsible for the Create/Update operations occurring on the PostsInThread table from the 'Server' schema
|
||||
* @param {RawPostsInThread[]} rootPosts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -350,10 +339,8 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
const postIds = rootPosts.map((postThread) => postThread.post_id);
|
||||
const rawPostsInThreads: RawPostsInThread[] = [];
|
||||
|
||||
const database = await this.getDatabase(POSTS_IN_THREAD);
|
||||
|
||||
// Retrieves all threads whereby their root_id can be one of the element in the postIds array
|
||||
const threads = (await database.collections.
|
||||
const threads = (await this.database.collections.
|
||||
get(POST).
|
||||
query(Q.where('root_id', Q.oneOf(postIds))).
|
||||
fetch()) as Post[];
|
||||
@@ -371,19 +358,18 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
if (rawPostsInThreads.length) {
|
||||
const postInThreadRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(rawPostsInThreads),
|
||||
database,
|
||||
recordOperator: preparePostInThreadRecord,
|
||||
transformer: transformPostInThreadRecord,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
})) as PostsInThread[];
|
||||
|
||||
if (postInThreadRecords?.length) {
|
||||
await this.batchOperations({database, models: postInThreadRecords});
|
||||
await this.batchRecords(postInThreadRecords);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostsInChannel: Handler responsible for the Create/Update operations occurring on the PostsInChannel entity from the 'Server' schema
|
||||
* handlePostsInChannel: Handler responsible for the Create/Update operations occurring on the PostsInChannel table from the 'Server' schema
|
||||
* @param {RawPost[]} posts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -412,21 +398,19 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
// Find highest 'create_at' value in chain; -1 means we are dealing with one item in the posts array
|
||||
const latest = sortedPosts[sortedPosts.length - 1].create_at;
|
||||
|
||||
const database = await this.getDatabase(POSTS_IN_CHANNEL);
|
||||
|
||||
// Find the records in the PostsInChannel table that have a matching channel_id
|
||||
// const chunks = (await database.collections.get(POSTS_IN_CHANNEL).query(Q.where('channel_id', channelId)).fetch()) as PostsInChannel[];
|
||||
const chunks = (await retrieveRecords({
|
||||
database,
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
condition: Q.where('channel_id', channelId),
|
||||
})) as PostsInChannel[];
|
||||
|
||||
const createPostsInChannelRecord = async () => {
|
||||
await this.executeInDatabase({
|
||||
await this.execute({
|
||||
createRaws: [{record: undefined, raw: {channel_id: channelId, earliest, latest}}],
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
recordOperator: preparePostsInChannelRecord,
|
||||
transformer: transformPostsInChannelRecord,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -459,7 +443,7 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
if (found) {
|
||||
// We have a potential chunk to plug nearby
|
||||
const potentialPosts = (await retrieveRecords({
|
||||
database,
|
||||
database: this.database,
|
||||
tableName: POST,
|
||||
condition: Q.where('create_at', earliest),
|
||||
})) as Post[];
|
||||
@@ -472,7 +456,7 @@ const PostHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
if (isChainable) {
|
||||
// Update this chunk's data in PostsInChannel table. earliest comes from tipOfChain while latest comes from chunk
|
||||
await database.action(async () => {
|
||||
await this.database.action(async () => {
|
||||
await targetChunk.update((postInChannel) => {
|
||||
postInChannel.earliest = earliest;
|
||||
});
|
||||
231
app/database/operator/server_data_operator/handlers/team.test.ts
Normal file
231
app/database/operator/server_data_operator/handlers/team.test.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordMyTeamEqualToRaw,
|
||||
isRecordSlashCommandEqualToRaw,
|
||||
isRecordTeamChannelHistoryEqualToRaw,
|
||||
isRecordTeamEqualToRaw,
|
||||
isRecordTeamMembershipEqualToRaw,
|
||||
isRecordTeamSearchHistoryEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformMyTeamRecord,
|
||||
transformSlashCommandRecord,
|
||||
transformTeamChannelHistoryRecord,
|
||||
transformTeamMembershipRecord,
|
||||
transformTeamRecord,
|
||||
transformTeamSearchHistoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/team';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Team Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleTeam: should write to the TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teams = [
|
||||
{
|
||||
id: 'rcgiyftm7jyrxnmdfdfa1osd8zswby',
|
||||
create_at: 1445538153952,
|
||||
update_at: 1588876392150,
|
||||
delete_at: 0,
|
||||
display_name: 'Contributors',
|
||||
name: 'core',
|
||||
description: '',
|
||||
email: '',
|
||||
type: 'O',
|
||||
company_name: '',
|
||||
allowed_domains: '',
|
||||
invite_id: 'codoy5s743rq5mk18i7u5dfdfksz7e',
|
||||
allow_open_invite: true,
|
||||
last_team_icon_update: 1525181587639,
|
||||
scheme_id: 'hbwgrncq1pfcdkpotzidfdmarn95o',
|
||||
group_constrained: null,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeam({
|
||||
teams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: teams,
|
||||
tableName: 'Team',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamEqualToRaw,
|
||||
transformer: transformTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamMemberships: should write to the TEAM_MEMBERSHIP table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamMemberships = [
|
||||
{
|
||||
team_id: 'a',
|
||||
user_id: 'ab',
|
||||
roles: '3ngdqe1e7tfcbmam4qgnxp91bw',
|
||||
delete_at: 0,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamMemberships({
|
||||
teamMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: teamMemberships,
|
||||
tableName: 'TeamMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamMembershipEqualToRaw,
|
||||
transformer: transformTeamMembershipRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyTeam: should write to the MY_TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myTeams = [
|
||||
{
|
||||
team_id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyTeam({
|
||||
myTeams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: myTeams,
|
||||
tableName: 'MyTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
transformer: transformMyTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamChannelHistory: should write to the TEAM_CHANNEL_HISTORY table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamChannelHistories = [
|
||||
{
|
||||
team_id: 'a',
|
||||
channel_ids: ['ca', 'cb'],
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamChannelHistory({
|
||||
teamChannelHistories,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: teamChannelHistories,
|
||||
tableName: 'TeamChannelHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
transformer: transformTeamChannelHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamSearchHistory: should write to the TEAM_SEARCH_HISTORY table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamSearchHistories = [
|
||||
{
|
||||
team_id: 'a',
|
||||
term: 'termA',
|
||||
display_term: 'termA',
|
||||
created_at: 1445538153952,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamSearchHistory({
|
||||
teamSearchHistories,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: teamSearchHistories,
|
||||
tableName: 'TeamSearchHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamSearchHistoryEqualToRaw,
|
||||
transformer: transformTeamSearchHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSlashCommand: should write to the SLASH_COMMAND table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const slashCommands = [
|
||||
{
|
||||
id: 'command_1',
|
||||
auto_complete: true,
|
||||
auto_complete_desc: 'mock_command',
|
||||
auto_complete_hint: 'hint',
|
||||
create_at: 1445538153952,
|
||||
creator_id: 'creator_id',
|
||||
delete_at: 1445538153952,
|
||||
description: 'description',
|
||||
display_name: 'display_name',
|
||||
icon_url: 'display_name',
|
||||
method: 'get',
|
||||
team_id: 'teamA',
|
||||
token: 'token',
|
||||
trigger: 'trigger',
|
||||
update_at: 1445538153953,
|
||||
url: 'url',
|
||||
username: 'userA',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleSlashCommand({
|
||||
slashCommands,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: slashCommands,
|
||||
tableName: 'SlashCommand',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordSlashCommandEqualToRaw,
|
||||
transformer: transformSlashCommandRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,15 +10,15 @@ import {
|
||||
isRecordTeamEqualToRaw,
|
||||
isRecordTeamMembershipEqualToRaw,
|
||||
isRecordTeamSearchHistoryEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
prepareMyTeamRecord,
|
||||
prepareSlashCommandRecord,
|
||||
prepareTeamChannelHistoryRecord,
|
||||
prepareTeamMembershipRecord,
|
||||
prepareTeamRecord,
|
||||
prepareTeamSearchHistoryRecord,
|
||||
} from '@database/operator/prepareRecords/team';
|
||||
transformMyTeamRecord,
|
||||
transformSlashCommandRecord,
|
||||
transformTeamChannelHistoryRecord,
|
||||
transformTeamMembershipRecord,
|
||||
transformTeamRecord,
|
||||
transformTeamSearchHistoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/team';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {
|
||||
HandleMyTeamArgs,
|
||||
@@ -28,12 +28,12 @@ import {
|
||||
HandleTeamMembershipArgs,
|
||||
HandleTeamSearchHistoryArgs,
|
||||
} from '@typings/database/database';
|
||||
import MyTeam from '@typings/database/my_team';
|
||||
import SlashCommand from '@typings/database/slash_command';
|
||||
import Team from '@typings/database/team';
|
||||
import TeamChannelHistory from '@typings/database/team_channel_history';
|
||||
import TeamMembership from '@typings/database/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/team_search_history';
|
||||
import MyTeam from '@typings/database/models/servers/my_team';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import TeamChannelHistory from '@typings/database/models/servers/team_channel_history';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/models/servers/team_search_history';
|
||||
|
||||
const {
|
||||
MY_TEAM,
|
||||
@@ -54,7 +54,7 @@ export interface TeamHandlerMix {
|
||||
|
||||
const TeamHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleTeamMemberships: Handler responsible for the Create/Update operations occurring on the TEAM_MEMBERSHIP entity from the 'Server' schema
|
||||
* handleTeamMemberships: Handler responsible for the Create/Update operations occurring on the TEAM_MEMBERSHIP table from the 'Server' schema
|
||||
* @param {HandleTeamMembershipArgs} teamMembershipsArgs
|
||||
* @param {RawTeamMembership[]} teamMembershipsArgs.teamMemberships
|
||||
* @param {boolean} teamMembershipsArgs.prepareRecordsOnly
|
||||
@@ -70,13 +70,13 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: teamMemberships, key: 'team_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamMemberships, key: 'team_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordTeamMembershipEqualToRaw,
|
||||
operator: prepareTeamMembershipRecord,
|
||||
rawValues,
|
||||
transformer: transformTeamMembershipRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_MEMBERSHIP,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
@@ -85,7 +85,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeam: Handler responsible for the Create/Update operations occurring on the TEAM entity from the 'Server' schema
|
||||
* handleTeam: Handler responsible for the Create/Update operations occurring on the TEAM table from the 'Server' schema
|
||||
* @param {HandleTeamArgs} teamsArgs
|
||||
* @param {RawTeam[]} teamsArgs.teams
|
||||
* @param {boolean} teamsArgs.prepareRecordsOnly
|
||||
@@ -101,14 +101,14 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: teams, key: 'id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teams, key: 'id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordTeamEqualToRaw,
|
||||
operator: prepareTeamRecord,
|
||||
transformer: transformTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM,
|
||||
});
|
||||
|
||||
@@ -116,7 +116,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeamChannelHistory: Handler responsible for the Create/Update operations occurring on the TEAM_CHANNEL_HISTORY entity from the 'Server' schema
|
||||
* handleTeamChannelHistory: Handler responsible for the Create/Update operations occurring on the TEAM_CHANNEL_HISTORY table from the 'Server' schema
|
||||
* @param {HandleTeamChannelHistoryArgs} teamChannelHistoriesArgs
|
||||
* @param {RawTeamChannelHistory[]} teamChannelHistoriesArgs.teamChannelHistories
|
||||
* @param {boolean} teamChannelHistoriesArgs.prepareRecordsOnly
|
||||
@@ -132,14 +132,14 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: teamChannelHistories, key: 'team_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamChannelHistories, key: 'team_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
operator: prepareTeamChannelHistoryRecord,
|
||||
transformer: transformTeamChannelHistoryRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_CHANNEL_HISTORY,
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeamSearchHistory: Handler responsible for the Create/Update operations occurring on the TEAM_SEARCH_HISTORY entity from the 'Server' schema
|
||||
* handleTeamSearchHistory: Handler responsible for the Create/Update operations occurring on the TEAM_SEARCH_HISTORY table from the 'Server' schema
|
||||
* @param {HandleTeamSearchHistoryArgs} teamSearchHistoriesArgs
|
||||
* @param {RawTeamSearchHistory[]} teamSearchHistoriesArgs.teamSearchHistories
|
||||
* @param {boolean} teamSearchHistoriesArgs.prepareRecordsOnly
|
||||
@@ -163,14 +163,14 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: teamSearchHistories, key: 'term'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamSearchHistories, key: 'term'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordTeamSearchHistoryEqualToRaw,
|
||||
operator: prepareTeamSearchHistoryRecord,
|
||||
transformer: transformTeamSearchHistoryRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_SEARCH_HISTORY,
|
||||
});
|
||||
|
||||
@@ -178,7 +178,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleSlashCommand: Handler responsible for the Create/Update operations occurring on the SLASH_COMMAND entity from the 'Server' schema
|
||||
* handleSlashCommand: Handler responsible for the Create/Update operations occurring on the SLASH_COMMAND table from the 'Server' schema
|
||||
* @param {HandleSlashCommandArgs} slashCommandsArgs
|
||||
* @param {RawSlashCommand[]} slashCommandsArgs.slashCommands
|
||||
* @param {boolean} slashCommandsArgs.prepareRecordsOnly
|
||||
@@ -194,14 +194,14 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: slashCommands, key: 'id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: slashCommands, key: 'id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordSlashCommandEqualToRaw,
|
||||
operator: prepareSlashCommandRecord,
|
||||
transformer: transformSlashCommandRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: SLASH_COMMAND,
|
||||
});
|
||||
|
||||
@@ -209,7 +209,7 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyTeam: Handler responsible for the Create/Update operations occurring on the MY_TEAM entity from the 'Server' schema
|
||||
* handleMyTeam: Handler responsible for the Create/Update operations occurring on the MY_TEAM table from the 'Server' schema
|
||||
* @param {HandleMyTeamArgs} myTeamsArgs
|
||||
* @param {RawMyTeam[]} myTeamsArgs.myTeams
|
||||
* @param {boolean} myTeamsArgs.prepareRecordsOnly
|
||||
@@ -225,14 +225,14 @@ const TeamHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: myTeams, key: 'team_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: myTeams, key: 'team_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
operator: prepareMyTeamRecord,
|
||||
transformer: transformMyTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_TEAM,
|
||||
});
|
||||
|
||||
222
app/database/operator/server_data_operator/handlers/user.test.ts
Normal file
222
app/database/operator/server_data_operator/handlers/user.test.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import {
|
||||
isRecordChannelMembershipEqualToRaw,
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
|
||||
describe('*** Operator: User Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleReactions: should write to both Reactions and CustomEmoji tables', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnPrepareRecords = jest.spyOn(operator, 'prepareRecords');
|
||||
const spyOnBatchOperation = jest.spyOn(operator, 'batchRecords');
|
||||
|
||||
await operator.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
create_at: 1608263728086,
|
||||
delete_at: 0,
|
||||
emoji_name: 'p4p1',
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
update_at: 1608263728077,
|
||||
user_id: 'ooumoqgq3bfiijzwbn8badznwc',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
// Called twice: Once for Reaction record and once for CustomEmoji record
|
||||
expect(spyOnPrepareRecords).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Only one batch operation for both tables
|
||||
expect(spyOnBatchOperation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('=> HandleUsers: should write to the User table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
create_at: 1599457495881,
|
||||
update_at: 1607683720173,
|
||||
delete_at: 0,
|
||||
username: 'a.l',
|
||||
auth_service: 'saml',
|
||||
email: 'a.l@mattermost.com',
|
||||
email_verified: true,
|
||||
is_bot: false,
|
||||
nickname: '',
|
||||
first_name: 'A',
|
||||
last_name: 'L',
|
||||
position: 'Mobile Engineer',
|
||||
roles: 'system_user',
|
||||
props: {},
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
auto_responder_active: false,
|
||||
auto_responder_message: 'Hello, I am out of office and unable to respond to messages.',
|
||||
comments: 'never',
|
||||
desktop_notification_sound: 'Hello',
|
||||
push_status: 'online',
|
||||
},
|
||||
last_password_update: 1604323112537,
|
||||
last_picture_update: 1604686302260,
|
||||
locale: 'en',
|
||||
timezone: {
|
||||
automaticTimezone: 'Indian/Mauritius',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
await operator.handleUsers({users, prepareRecordsOnly: false});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: users,
|
||||
tableName: 'User',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordUserEqualToRaw,
|
||||
transformer: transformUserRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandlePreferences: should write to the PREFERENCE table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const preferences = [
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'group_channel_show',
|
||||
name: 'qj91hepgjfn6xr4acm5xzd8zoc',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'notifications',
|
||||
name: 'email_interval',
|
||||
value: '30',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'theme',
|
||||
name: '',
|
||||
value:
|
||||
'{"awayIndicator":"#c1b966","buttonBg":"#4cbba4","buttonColor":"#ffffff","centerChannelBg":"#2f3e4e","centerChannelColor":"#dddddd","codeTheme":"solarized-dark","dndIndicator":"#e81023","errorTextColor":"#ff6461","image":"/static/files/0b8d56c39baf992e5e4c58d74fde0fd6.png","linkColor":"#a4ffeb","mentionBg":"#b74a4a","mentionColor":"#ffffff","mentionHighlightBg":"#984063","mentionHighlightLink":"#a4ffeb","newMessageSeparator":"#5de5da","onlineIndicator":"#65dcc8","sidebarBg":"#1b2c3e","sidebarHeaderBg":"#1b2c3e","sidebarHeaderTextColor":"#ffffff","sidebarText":"#ffffff","sidebarTextActiveBorder":"#66b9a7","sidebarTextActiveColor":"#ffffff","sidebarTextHoverBg":"#4a5664","sidebarUnreadText":"#ffffff","type":"Mattermost Dark"}',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'tutorial_step',
|
||||
name: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
value: '2',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handlePreferences({
|
||||
preferences,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: preferences,
|
||||
tableName: 'Preference',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordPreferenceEqualToRaw,
|
||||
transformer: transformPreferenceRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelMembership: should write to the CHANNEL_MEMBERSHIP table', async () => {
|
||||
expect.assertions(2);
|
||||
const channelMemberships = [
|
||||
{
|
||||
channel_id: '17bfnb1uwb8epewp4q3x3rx9go',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'wqyby5r5pinxxdqhoaomtacdhc',
|
||||
last_viewed_at: 1613667352029,
|
||||
msg_count: 3864,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1613667352029,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
{
|
||||
channel_id: '1yw6gxfr4bn1jbyp9nr7d53yew',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'channel_user',
|
||||
last_viewed_at: 1615300540549,
|
||||
msg_count: 16,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'all',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1615300540549,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
await operator.handleChannelMembership({
|
||||
channelMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: channelMemberships,
|
||||
tableName: 'ChannelMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelMembershipEqualToRaw,
|
||||
transformer: transformChannelMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,18 +7,18 @@ import {
|
||||
isRecordChannelMembershipEqualToRaw,
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/comparators';
|
||||
import {prepareCustomEmojiRecord} from '@database/operator/prepareRecords/general';
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformCustomEmojiRecord} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {
|
||||
prepareChannelMembershipRecord,
|
||||
preparePreferenceRecord,
|
||||
prepareReactionRecord,
|
||||
prepareUserRecord,
|
||||
} from '@database/operator/prepareRecords/user';
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformReactionRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
import {getRawRecordPairs, getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {sanitizeReactions} from '@database/operator/utils/reaction';
|
||||
import ChannelMembership from '@typings/database/channel_membership';
|
||||
import CustomEmoji from '@typings/database/custom_emoji';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import CustomEmoji from '@typings/database/models/servers/custom_emoji';
|
||||
import {
|
||||
HandleChannelMembershipArgs,
|
||||
HandlePreferencesArgs,
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
HandleUsersArgs,
|
||||
RawReaction,
|
||||
} from '@typings/database/database';
|
||||
import Preference from '@typings/database/preference';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import User from '@typings/database/user';
|
||||
import Preference from '@typings/database/models/servers/preference';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CHANNEL_MEMBERSHIP,
|
||||
@@ -39,20 +39,20 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface UserHandlerMix {
|
||||
handleChannelMembership : ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => ChannelMembership[];
|
||||
handlePreferences : ({preferences, prepareRecordsOnly}: HandlePreferencesArgs) => Preference[];
|
||||
handleReactions : ({reactions, prepareRecordsOnly}: HandleReactionsArgs) =>(Reaction | CustomEmoji)[];
|
||||
handleUsers : ({users, prepareRecordsOnly}: HandleUsersArgs) => User[];
|
||||
handleChannelMembership : ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => Promise<ChannelMembership[]>;
|
||||
handlePreferences : ({preferences, prepareRecordsOnly}: HandlePreferencesArgs) => Promise<Preference[]>;
|
||||
handleReactions : ({reactions, prepareRecordsOnly}: HandleReactionsArgs) => Promise<(Reaction | CustomEmoji)[]>;
|
||||
handleUsers : ({users, prepareRecordsOnly}: HandleUsersArgs) => Promise<User[]>;
|
||||
}
|
||||
|
||||
const UserHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleChannelMembership: Handler responsible for the Create/Update operations occurring on the CHANNEL_MEMBERSHIP entity from the 'Server' schema
|
||||
* handleChannelMembership: Handler responsible for the Create/Update operations occurring on the CHANNEL_MEMBERSHIP table from the 'Server' schema
|
||||
* @param {HandleChannelMembershipArgs} channelMembershipsArgs
|
||||
* @param {RawChannelMembership[]} channelMembershipsArgs.channelMemberships
|
||||
* @param {boolean} channelMembershipsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {ChannelMembership[]}
|
||||
* @returns {Promise<ChannelMembership[]>}
|
||||
*/
|
||||
handleChannelMembership = async ({channelMemberships, prepareRecordsOnly = true}: HandleChannelMembershipArgs) => {
|
||||
let records: ChannelMembership[] = [];
|
||||
@@ -63,14 +63,14 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: channelMemberships, key: 'channel_id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: channelMemberships, key: 'channel_id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordChannelMembershipEqualToRaw,
|
||||
operator: prepareChannelMembershipRecord,
|
||||
transformer: transformChannelMembershipRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL_MEMBERSHIP,
|
||||
});
|
||||
|
||||
@@ -78,12 +78,12 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePreferences: Handler responsible for the Create/Update operations occurring on the PREFERENCE entity from the 'Server' schema
|
||||
* handlePreferences: Handler responsible for the Create/Update operations occurring on the PREFERENCE table from the 'Server' schema
|
||||
* @param {HandlePreferencesArgs} preferencesArgs
|
||||
* @param {RawPreference[]} preferencesArgs.preferences
|
||||
* @param {boolean} preferencesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Preference[]}
|
||||
* @returns {Promise<Preference[]>}
|
||||
*/
|
||||
handlePreferences = async ({preferences, prepareRecordsOnly = true}: HandlePreferencesArgs) => {
|
||||
let records: Preference[] = [];
|
||||
@@ -94,14 +94,14 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: preferences, key: 'name'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: preferences, key: 'name'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordPreferenceEqualToRaw,
|
||||
operator: preparePreferenceRecord,
|
||||
transformer: transformPreferenceRecord,
|
||||
prepareRecordsOnly,
|
||||
rawValues,
|
||||
createOrUpdateRawValues,
|
||||
tableName: PREFERENCE,
|
||||
});
|
||||
|
||||
@@ -109,12 +109,12 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
/**
|
||||
* handleReactions: Handler responsible for the Create/Update operations occurring on the Reaction entity from the 'Server' schema
|
||||
* handleReactions: Handler responsible for the Create/Update operations occurring on the Reaction table from the 'Server' schema
|
||||
* @param {HandleReactionsArgs} handleReactions
|
||||
* @param {RawReaction[]} handleReactions.reactions
|
||||
* @param {boolean} handleReactions.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {(Reaction| CustomEmoji)[]}
|
||||
* @returns {Promise<(Reaction| CustomEmoji)[]>}
|
||||
*/
|
||||
handleReactions = async ({reactions, prepareRecordsOnly}: HandleReactionsArgs) => {
|
||||
let batchRecords: (Reaction| CustomEmoji)[] = [];
|
||||
@@ -127,14 +127,12 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: reactions, key: 'emoji_name'}) as RawReaction[];
|
||||
|
||||
const database = await this.getDatabase(REACTION);
|
||||
|
||||
const {
|
||||
createEmojis,
|
||||
createReactions,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database,
|
||||
database: this.database,
|
||||
post_id: reactions[0].post_id,
|
||||
rawReactions: rawValues,
|
||||
});
|
||||
@@ -143,8 +141,7 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
// Prepares record for model Reactions
|
||||
const reactionsRecords = (await this.prepareRecords({
|
||||
createRaws: createReactions,
|
||||
database,
|
||||
recordOperator: prepareReactionRecord,
|
||||
transformer: transformReactionRecord,
|
||||
tableName: REACTION,
|
||||
})) as Reaction[];
|
||||
batchRecords = batchRecords.concat(reactionsRecords);
|
||||
@@ -154,8 +151,7 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
// Prepares records for model CustomEmoji
|
||||
const emojiRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(createEmojis),
|
||||
database,
|
||||
recordOperator: prepareCustomEmojiRecord,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
tableName: CUSTOM_EMOJI,
|
||||
})) as CustomEmoji[];
|
||||
batchRecords = batchRecords.concat(emojiRecords);
|
||||
@@ -168,22 +164,19 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
}
|
||||
|
||||
if (batchRecords?.length) {
|
||||
await this.batchOperations({
|
||||
database,
|
||||
models: batchRecords,
|
||||
});
|
||||
await this.batchRecords(batchRecords);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handleUsers: Handler responsible for the Create/Update operations occurring on the User entity from the 'Server' schema
|
||||
* handleUsers: Handler responsible for the Create/Update operations occurring on the User table from the 'Server' schema
|
||||
* @param {HandleUsersArgs} usersArgs
|
||||
* @param {RawUser[]} usersArgs.users
|
||||
* @param {boolean} usersArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {User[]}
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
handleUsers = async ({users, prepareRecordsOnly = true}: HandleUsersArgs) => {
|
||||
let records: User[] = [];
|
||||
@@ -194,13 +187,13 @@ const UserHandler = (superclass: any) => class extends superclass {
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: users, key: 'id'});
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: users, key: 'id'});
|
||||
|
||||
records = await this.handleEntityRecords({
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordUserEqualToRaw,
|
||||
operator: prepareUserRecord,
|
||||
rawValues,
|
||||
transformer: transformUserRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: USER,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
29
app/database/operator/server_data_operator/index.ts
Normal file
29
app/database/operator/server_data_operator/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ServerDataOperatorBase from '@database/operator/server_data_operator/handlers';
|
||||
import ChannelHandler, {ChannelHandlerMix} from '@database/operator/server_data_operator/handlers/channel';
|
||||
import GroupHandler, {GroupHandlerMix} from '@database/operator/server_data_operator/handlers/group';
|
||||
import PostHandler, {PostHandlerMix} from '@database/operator/server_data_operator/handlers/post';
|
||||
import TeamHandler, {TeamHandlerMix} from '@database/operator/server_data_operator/handlers/team';
|
||||
import UserHandler, {UserHandlerMix} from '@database/operator/server_data_operator/handlers/user';
|
||||
import mix from '@utils/mix';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
|
||||
interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, TeamHandlerMix {}
|
||||
|
||||
class ServerDataOperator extends mix(ServerDataOperatorBase).with(
|
||||
ChannelHandler,
|
||||
GroupHandler,
|
||||
PostHandler,
|
||||
TeamHandler,
|
||||
UserHandler,
|
||||
) {
|
||||
// eslint-disable-next-line no-useless-constructor
|
||||
constructor(database: Database) {
|
||||
super(database);
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerDataOperator;
|
||||
@@ -2,22 +2,22 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
prepareChannelInfoRecord,
|
||||
prepareChannelRecord,
|
||||
prepareMyChannelRecord,
|
||||
prepareMyChannelSettingsRecord,
|
||||
} from '@database/operator/prepareRecords/channel';
|
||||
transformChannelInfoRecord,
|
||||
transformChannelRecord,
|
||||
transformMyChannelRecord,
|
||||
transformMyChannelSettingsRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/channel';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** CHANNEL Prepare Records Test ***', () => {
|
||||
it('=> prepareChannelRecord: should return an array of type Channel', async () => {
|
||||
it('=> transformChannelRecord: should return an array of type Channel', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'channel_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareChannelRecord({
|
||||
const preparedRecords = await transformChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -49,13 +49,13 @@ describe('*** CHANNEL Prepare Records Test ***', () => {
|
||||
expect(preparedRecords.collection.modelClass.name).toBe('Channel');
|
||||
});
|
||||
|
||||
it('=> prepareMyChannelSettingsRecord: should return an array of type MyChannelSettings', async () => {
|
||||
it('=> transformMyChannelSettingsRecord: should return an array of type MyChannelSettings', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'channel_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareMyChannelSettingsRecord({
|
||||
const preparedRecords = await transformMyChannelSettingsRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -79,13 +79,13 @@ describe('*** CHANNEL Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('MyChannelSettings');
|
||||
});
|
||||
|
||||
it('=> prepareChannelInfoRecord: should return an array of type ChannelInfo', async () => {
|
||||
it('=> transformChannelInfoRecord: should return an array of type ChannelInfo', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'channel_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareChannelInfoRecord({
|
||||
const preparedRecords = await transformChannelInfoRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -105,13 +105,13 @@ describe('*** CHANNEL Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('ChannelInfo');
|
||||
});
|
||||
|
||||
it('=> prepareMyChannelRecord: should return an array of type MyChannel', async () => {
|
||||
it('=> transformMyChannelRecord: should return an array of type MyChannel', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'channel_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareMyChannelRecord({
|
||||
const preparedRecords = await transformMyChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -2,19 +2,19 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import Channel from '@typings/database/channel';
|
||||
import ChannelInfo from '@typings/database/channel_info';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import ChannelInfo from '@typings/database/models/servers/channel_info';
|
||||
import {
|
||||
DataFactoryArgs,
|
||||
TransformerArgs,
|
||||
RawChannel,
|
||||
RawChannelInfo,
|
||||
RawMyChannel,
|
||||
RawMyChannelSettings,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import MyChannel from '@typings/database/my_channel';
|
||||
import MyChannelSettings from '@typings/database/my_channel_settings';
|
||||
import MyChannel from '@typings/database/models/servers/my_channel';
|
||||
import MyChannelSettings from '@typings/database/models/servers/my_channel_settings';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
@@ -24,19 +24,19 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* prepareChannelRecord: Prepares record of entity 'CHANNEL' from the SERVER database for update or create actions.
|
||||
* transformChannelRecord: Prepares a record of the SERVER database 'Channel' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareChannelRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformChannelRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawChannel;
|
||||
const record = value.record as Channel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (channel: Channel) => {
|
||||
const fieldsMapper = (channel: Channel) => {
|
||||
channel._raw.id = isCreateAction ? (raw?.id ?? channel.id) : record.id;
|
||||
channel.createAt = raw.create_at;
|
||||
channel.creatorId = raw.creator_id;
|
||||
@@ -53,23 +53,23 @@ export const prepareChannelRecord = ({action, database, value}: DataFactoryArgs)
|
||||
database,
|
||||
tableName: CHANNEL,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareMyChannelSettingsRecord: Prepares record of entity 'MY_CHANNEL_SETTINGS' from the SERVER database for update or create actions.
|
||||
* transformMyChannelSettingsRecord: Prepares a record of the SERVER database 'MyChannelSettings' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareMyChannelSettingsRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformMyChannelSettingsRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawMyChannelSettings;
|
||||
const record = value.record as MyChannelSettings;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (myChannelSetting: MyChannelSettings) => {
|
||||
const fieldsMapper = (myChannelSetting: MyChannelSettings) => {
|
||||
myChannelSetting._raw.id = isCreateAction ? myChannelSetting.id : record.id;
|
||||
myChannelSetting.channelId = raw.channel_id;
|
||||
myChannelSetting.notifyProps = raw.notify_props;
|
||||
@@ -80,29 +80,29 @@ export const prepareMyChannelSettingsRecord = ({action, database, value}: DataFa
|
||||
database,
|
||||
tableName: MY_CHANNEL_SETTINGS,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareChannelInfoRecord: Prepares record of entity 'CHANNEL_INFO' from the SERVER database for update or create actions.
|
||||
* transformChannelInfoRecord: Prepares a record of the SERVER database 'ChannelInfo' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareChannelInfoRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformChannelInfoRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawChannelInfo;
|
||||
const record = value.record as ChannelInfo;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (channelInfo: ChannelInfo) => {
|
||||
const fieldsMapper = (channelInfo: ChannelInfo) => {
|
||||
channelInfo._raw.id = isCreateAction ? channelInfo.id : record.id;
|
||||
channelInfo.channelId = raw.channel_id;
|
||||
channelInfo.guestCount = raw.guest_count;
|
||||
channelInfo.header = raw.header;
|
||||
channelInfo.memberCount = raw.member_count;
|
||||
channelInfo.pinned_post_count = raw.pinned_post_count;
|
||||
channelInfo.pinnedPostCount = raw.pinned_post_count;
|
||||
channelInfo.purpose = raw.purpose;
|
||||
};
|
||||
|
||||
@@ -111,23 +111,23 @@ export const prepareChannelInfoRecord = ({action, database, value}: DataFactoryA
|
||||
database,
|
||||
tableName: CHANNEL_INFO,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareMyChannelRecord: Prepares record of entity 'MY_CHANNEL' from the SERVER database for update or create actions.
|
||||
* transformMyChannelRecord: Prepares a record of the SERVER database 'MyChannel' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareMyChannelRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformMyChannelRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawMyChannel;
|
||||
const record = value.record as MyChannel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (myChannel: MyChannel) => {
|
||||
const fieldsMapper = (myChannel: MyChannel) => {
|
||||
myChannel._raw.id = isCreateAction ? myChannel.id : record.id;
|
||||
myChannel.channelId = raw.channel_id;
|
||||
myChannel.roles = raw.roles;
|
||||
@@ -142,7 +142,7 @@ export const prepareMyChannelRecord = ({action, database, value}: DataFactoryArg
|
||||
database,
|
||||
tableName: MY_CHANNEL,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
transformCustomEmojiRecord,
|
||||
transformRoleRecord,
|
||||
transformSystemRecord,
|
||||
transformTermsOfServiceRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** Role Prepare Records Test ***', () => {
|
||||
it('=> transformRoleRecord: should return an array of type Role', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformRoleRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'role-1',
|
||||
name: 'role-name-1',
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Role');
|
||||
});
|
||||
});
|
||||
|
||||
describe('*** System Prepare Records Test ***', () => {
|
||||
it('=> transformSystemRecord: should return an array of type System', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformSystemRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {id: 'system-1', name: 'system-name-1', value: 'system'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('System');
|
||||
});
|
||||
});
|
||||
|
||||
describe('*** TOS Prepare Records Test ***', () => {
|
||||
it('=> transformTermsOfServiceRecord: should return an array of type TermsOfService', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformTermsOfServiceRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'tos-1',
|
||||
accepted_at: 1,
|
||||
create_at: 1613667352029,
|
||||
user_id: 'user1613667352029',
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('TermsOfService');
|
||||
});
|
||||
});
|
||||
|
||||
describe('*** CustomEmoj Prepare Records Test ***', () => {
|
||||
it('=> transformCustomEmojiRecord: should return an array of type CustomEmoji', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'isolated_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformCustomEmojiRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('CustomEmoji');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import CustomEmoji from '@typings/database/models/servers/custom_emoji';
|
||||
import {
|
||||
TransformerArgs,
|
||||
RawCustomEmoji,
|
||||
RawRole,
|
||||
RawSystem,
|
||||
RawTermsOfService,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import Role from '@typings/database/models/servers/role';
|
||||
import System from '@typings/database/models/servers/system';
|
||||
import TermsOfService from '@typings/database/models/servers/terms_of_service';
|
||||
|
||||
const {
|
||||
CUSTOM_EMOJI,
|
||||
ROLE,
|
||||
SYSTEM,
|
||||
TERMS_OF_SERVICE,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* transformCustomEmojiRecord: Prepares a record of the SERVER database 'CustomEmoji' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformCustomEmojiRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawCustomEmoji;
|
||||
const record = value.record as CustomEmoji;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const fieldsMapper = (emoji: CustomEmoji) => {
|
||||
emoji._raw.id = isCreateAction ? (raw?.id ?? emoji.id) : record.id;
|
||||
emoji.name = raw.name;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: CUSTOM_EMOJI,
|
||||
value,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* transformRoleRecord: Prepares a record of the SERVER database 'Role' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformRoleRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawRole;
|
||||
const record = value.record as Role;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const fieldsMapper = (role: Role) => {
|
||||
role._raw.id = isCreateAction ? (raw?.id ?? role.id) : record.id;
|
||||
role.name = raw?.name;
|
||||
role.permissions = raw?.permissions;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: ROLE,
|
||||
value,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* transformSystemRecord: Prepares a record of the SERVER database 'System' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformSystemRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawSystem;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const fieldsMapper = (system: System) => {
|
||||
system.name = raw?.name;
|
||||
system.value = raw?.value;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: SYSTEM,
|
||||
value,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* transformTermsOfServiceRecord: Prepares a record of the SERVER database 'TermsOfService' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const transformTermsOfServiceRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawTermsOfService;
|
||||
const record = value.record as TermsOfService;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const fieldsMapper = (tos: TermsOfService) => {
|
||||
tos._raw.id = isCreateAction ? (raw?.id ?? tos.id) : record.id;
|
||||
tos.acceptedAt = raw?.accepted_at;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: TERMS_OF_SERVICE,
|
||||
value,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
@@ -2,22 +2,22 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
prepareGroupMembershipRecord,
|
||||
prepareGroupRecord,
|
||||
prepareGroupsInChannelRecord,
|
||||
prepareGroupsInTeamRecord,
|
||||
} from '@database/operator/prepareRecords/group';
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** GROUP Prepare Records Test ***', () => {
|
||||
it('=> prepareGroupRecord: should return an array of type Group', async () => {
|
||||
it('=> transformGroupRecord: should return an array of type Group', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareGroupRecord({
|
||||
const preparedRecords = await transformGroupRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -41,13 +41,13 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Group');
|
||||
});
|
||||
|
||||
it('=> prepareGroupsInTeamRecord: should return an array of type GroupsInTeam', async () => {
|
||||
it('=> transformGroupsInTeamRecord: should return an array of type GroupsInTeam', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareGroupsInTeamRecord({
|
||||
const preparedRecords = await transformGroupsInTeamRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -69,13 +69,13 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsInTeam');
|
||||
});
|
||||
|
||||
it('=> prepareGroupsInChannelRecord: should return an array of type GroupsInChannel', async () => {
|
||||
it('=> transformGroupsInChannelRecord: should return an array of type GroupsInChannel', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareGroupsInChannelRecord({
|
||||
const preparedRecords = await transformGroupsInChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -100,13 +100,13 @@ describe('*** GROUP Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsInChannel');
|
||||
});
|
||||
|
||||
it('=> prepareGroupMembershipRecord: should return an array of type GroupMembership', async () => {
|
||||
it('=> transformGroupMembershipRecord: should return an array of type GroupMembership', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareGroupMembershipRecord({
|
||||
const preparedRecords = await transformGroupMembershipRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -2,19 +2,19 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import {
|
||||
DataFactoryArgs,
|
||||
TransformerArgs,
|
||||
RawGroup,
|
||||
RawGroupMembership,
|
||||
RawGroupsInChannel,
|
||||
RawGroupsInTeam,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import Group from '@typings/database/group';
|
||||
import GroupMembership from '@typings/database/group_membership';
|
||||
import GroupsInChannel from '@typings/database/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/groups_in_team';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
|
||||
const {
|
||||
GROUP,
|
||||
@@ -24,19 +24,19 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* prepareGroupMembershipRecord: Prepares record of entity 'GROUP_MEMBERSHIP' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformGroupMembershipRecord: Prepares a record of the SERVER database 'GroupMembership' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareGroupMembershipRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformGroupMembershipRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawGroupMembership;
|
||||
const record = value.record as GroupMembership;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (groupMember: GroupMembership) => {
|
||||
const fieldsMapper = (groupMember: GroupMembership) => {
|
||||
groupMember._raw.id = isCreateAction ? (raw?.id ?? groupMember.id) : record.id;
|
||||
groupMember.groupId = raw.group_id;
|
||||
groupMember.userId = raw.user_id;
|
||||
@@ -47,24 +47,24 @@ export const prepareGroupMembershipRecord = ({action, database, value}: DataFact
|
||||
database,
|
||||
tableName: GROUP_MEMBERSHIP,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareGroupRecord: Prepares record of entity 'GROUP' from the SERVER database for update or create actions.
|
||||
* transformGroupRecord: Prepares a record of the SERVER database 'Group' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareGroupRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformGroupRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawGroup;
|
||||
const record = value.record as Group;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (group: Group) => {
|
||||
const fieldsMapper = (group: Group) => {
|
||||
group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record.id;
|
||||
group.name = raw.name;
|
||||
group.displayName = raw.display_name;
|
||||
@@ -75,23 +75,23 @@ export const prepareGroupRecord = ({action, database, value}: DataFactoryArgs) =
|
||||
database,
|
||||
tableName: GROUP,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareGroupsInTeamRecord: Prepares record of entity 'GROUPS_IN_TEAM' from the SERVER database for update or create actions.
|
||||
* transformGroupsInTeamRecord: Prepares a record of the SERVER database 'GroupsInTeam' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareGroupsInTeamRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformGroupsInTeamRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawGroupsInTeam;
|
||||
const record = value.record as GroupsInTeam;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (groupsInTeam: GroupsInTeam) => {
|
||||
const fieldsMapper = (groupsInTeam: GroupsInTeam) => {
|
||||
groupsInTeam._raw.id = isCreateAction ? groupsInTeam.id : record.id;
|
||||
groupsInTeam.teamId = raw.team_id;
|
||||
groupsInTeam.groupId = raw.group_id;
|
||||
@@ -102,23 +102,23 @@ export const prepareGroupsInTeamRecord = ({action, database, value}: DataFactory
|
||||
database,
|
||||
tableName: GROUPS_IN_TEAM,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareGroupsInChannelRecord: Prepares record of entity 'GROUPS_IN_CHANNEL' from the SERVER database for update or create actions.
|
||||
* transformGroupsInChannelRecord: Prepares a record of the SERVER database 'GroupsInChannel' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareGroupsInChannelRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformGroupsInChannelRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawGroupsInChannel;
|
||||
const record = value.record as GroupsInChannel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (groupsInChannel: GroupsInChannel) => {
|
||||
const fieldsMapper = (groupsInChannel: GroupsInChannel) => {
|
||||
groupsInChannel._raw.id = isCreateAction ? groupsInChannel.id : record.id;
|
||||
groupsInChannel.channelId = raw.channel_id;
|
||||
groupsInChannel.groupId = raw.group_id;
|
||||
@@ -131,6 +131,6 @@ export const prepareGroupsInChannelRecord = ({action, database, value}: DataFact
|
||||
database,
|
||||
tableName: GROUPS_IN_CHANNEL,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
@@ -2,18 +2,18 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
import {DataFactoryArgs} from '@typings/database/database';
|
||||
import {TransformerArgs} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
/**
|
||||
* prepareBaseRecord: This is the last step for each operator and depending on the 'action', it will either prepare an
|
||||
* existing record for UPDATE or prepare a collection for CREATE
|
||||
*
|
||||
* @param {DataFactoryArgs} operatorBase
|
||||
* @param {TransformerArgs} operatorBase
|
||||
* @param {Database} operatorBase.database
|
||||
* @param {string} operatorBase.tableName
|
||||
* @param {RecordPair} operatorBase.value
|
||||
* @param {((DataFactoryArgs) => void)} operatorBase.generator
|
||||
* @param {((TransformerArgs) => void)} operatorBase.generator
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareBaseRecord = async ({
|
||||
@@ -21,12 +21,12 @@ export const prepareBaseRecord = async ({
|
||||
database,
|
||||
tableName,
|
||||
value,
|
||||
generator,
|
||||
}: DataFactoryArgs): Promise<Model> => {
|
||||
fieldsMapper,
|
||||
}: TransformerArgs): Promise<Model> => {
|
||||
if (action === OperationType.UPDATE) {
|
||||
const record = value.record as Model;
|
||||
return record.prepareUpdate(() => generator!(record));
|
||||
return record.prepareUpdate(() => fieldsMapper!(record));
|
||||
}
|
||||
|
||||
return database.collections.get(tableName!).prepareCreate(generator);
|
||||
return database.collections.get(tableName!).prepareCreate(fieldsMapper);
|
||||
};
|
||||
@@ -2,26 +2,24 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
prepareDraftRecord,
|
||||
prepareFileRecord,
|
||||
preparePostInThreadRecord,
|
||||
preparePostMetadataRecord,
|
||||
preparePostRecord,
|
||||
preparePostsInChannelRecord,
|
||||
} from '@database/operator/prepareRecords/post';
|
||||
transformDraftRecord,
|
||||
transformFileRecord,
|
||||
transformPostInThreadRecord,
|
||||
transformPostMetadataRecord,
|
||||
transformPostRecord,
|
||||
transformPostsInChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** POST Prepare Records Test ***', () => {
|
||||
it('=> preparePostRecord: should return an array of type Post', async () => {
|
||||
it('=> transformPostRecord: should return an array of type Post', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await preparePostRecord({
|
||||
const preparedRecords = await transformPostRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -38,7 +36,7 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
root_id: 'ps81iqbesfby8jayz7owg4yypoo',
|
||||
parent_id: 'ps81iqbddesfby8jayz7owg4yypoo',
|
||||
original_id: '',
|
||||
message: 'Testing operator post',
|
||||
message: 'Testing composer post',
|
||||
type: '',
|
||||
props: {},
|
||||
hashtags: '',
|
||||
@@ -55,13 +53,13 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Post');
|
||||
});
|
||||
|
||||
it('=> preparePostInThreadRecord: should return an array of type PostsInThread', async () => {
|
||||
it('=> transformPostInThreadRecord: should return an array of type PostsInThread', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await preparePostInThreadRecord({
|
||||
const preparedRecords = await transformPostInThreadRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -81,13 +79,13 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('=> prepareFileRecord: should return an array of type File', async () => {
|
||||
it('=> transformFileRecord: should return an array of type File', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareFileRecord({
|
||||
const preparedRecords = await transformFileRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -110,13 +108,13 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('File');
|
||||
});
|
||||
|
||||
it('=> preparePostMetadataRecord: should return an array of type PostMetadata', async () => {
|
||||
it('=> transformPostMetadataRecord: should return an array of type PostMetadata', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await preparePostMetadataRecord({
|
||||
const preparedRecords = await transformPostMetadataRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -134,13 +132,13 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('PostMetadata');
|
||||
});
|
||||
|
||||
it('=> prepareDraftRecord: should return an array of type Draft', async () => {
|
||||
it('=> transformDraftRecord: should return an array of type Draft', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareDraftRecord({
|
||||
const preparedRecords = await transformDraftRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -159,13 +157,13 @@ describe('*** POST Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Draft');
|
||||
});
|
||||
|
||||
it('=> preparePostsInChannelRecord: should return an array of type PostsInChannel', async () => {
|
||||
it('=> transformPostsInChannelRecord: should return an array of type PostsInChannel', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'post_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await preparePostsInChannelRecord({
|
||||
const preparedRecords = await transformPostsInChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import {
|
||||
DataFactoryArgs,
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import type{
|
||||
TransformerArgs,
|
||||
RawDraft,
|
||||
RawFile,
|
||||
RawPost,
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
RawPostsInChannel,
|
||||
RawPostsInThread,
|
||||
} from '@typings/database/database';
|
||||
import Draft from '@typings/database/draft';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import File from '@typings/database/file';
|
||||
import Post from '@typings/database/post';
|
||||
import PostMetadata from '@typings/database/post_metadata';
|
||||
import PostsInChannel from '@typings/database/posts_in_channel';
|
||||
import PostsInThread from '@typings/database/posts_in_thread';
|
||||
import File from '@typings/database/models/servers/file';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import PostMetadata from '@typings/database/models/servers/post_metadata';
|
||||
import PostsInChannel from '@typings/database/models/servers/posts_in_channel';
|
||||
import PostsInThread from '@typings/database/models/servers/posts_in_thread';
|
||||
|
||||
const {
|
||||
DRAFT,
|
||||
@@ -31,19 +31,19 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* preparePostRecord: Prepares record of entity 'Post' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformPostRecord: Prepares a record of the SERVER database 'Post' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const preparePostRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformPostRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawPost;
|
||||
const record = value.record as Post;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (post: Post) => {
|
||||
const fieldsMapper = (post: Post) => {
|
||||
post._raw.id = isCreateAction ? (raw?.id ?? post.id) : record.id;
|
||||
post.channelId = raw.channel_id;
|
||||
post.createAt = raw.create_at;
|
||||
@@ -66,23 +66,23 @@ export const preparePostRecord = ({action, database, value}: DataFactoryArgs) =>
|
||||
database,
|
||||
tableName: POST,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* preparePostInThreadRecord: Prepares record of entity 'POSTS_IN_THREAD' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformPostInThreadRecord: Prepares a record of the SERVER database 'PostsInThread' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const preparePostInThreadRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformPostInThreadRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawPostsInThread;
|
||||
const record = value.record as PostsInThread;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (postsInThread: PostsInThread) => {
|
||||
const fieldsMapper = (postsInThread: PostsInThread) => {
|
||||
postsInThread.postId = isCreateAction ? raw.post_id : record.id;
|
||||
postsInThread.earliest = raw.earliest;
|
||||
postsInThread.latest = raw.latest!;
|
||||
@@ -93,24 +93,24 @@ export const preparePostInThreadRecord = ({action, database, value}: DataFactory
|
||||
database,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareFileRecord: Prepares record of entity 'FILE' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformFileRecord: Prepares a record of the SERVER database 'Files' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareFileRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformFileRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawFile;
|
||||
const record = value.record as File;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (file: File) => {
|
||||
const fieldsMapper = (file: File) => {
|
||||
file._raw.id = isCreateAction ? (raw?.id ?? file.id) : record.id;
|
||||
file.postId = raw.post_id;
|
||||
file.name = raw.name;
|
||||
@@ -128,23 +128,23 @@ export const prepareFileRecord = ({action, database, value}: DataFactoryArgs) =>
|
||||
database,
|
||||
tableName: FILE,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* preparePostMetadataRecord: Prepares record of entity 'POST_METADATA' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformPostMetadataRecord: Prepares a record of the SERVER database 'PostMetadata' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const preparePostMetadataRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformPostMetadataRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawPostMetadata;
|
||||
const record = value.record as PostMetadata;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (postMeta: PostMetadata) => {
|
||||
const fieldsMapper = (postMeta: PostMetadata) => {
|
||||
postMeta._raw.id = isCreateAction ? postMeta.id : record.id;
|
||||
postMeta.data = raw.data;
|
||||
postMeta.postId = raw.postId;
|
||||
@@ -156,23 +156,23 @@ export const preparePostMetadataRecord = ({action, database, value}: DataFactory
|
||||
database,
|
||||
tableName: POST_METADATA,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareDraftRecord: Prepares record of entity 'DRAFT' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformDraftRecord: Prepares a record of the SERVER database 'Draft' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareDraftRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformDraftRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const emptyFileInfo: FileInfo[] = [];
|
||||
const raw = value.raw as RawDraft;
|
||||
|
||||
// We use the raw id as Draft is client side only and we would only be creating/deleting drafts
|
||||
const generator = (draft: Draft) => {
|
||||
const fieldsMapper = (draft: Draft) => {
|
||||
draft._raw.id = draft.id;
|
||||
draft.rootId = raw?.root_id ?? '';
|
||||
draft.message = raw?.message ?? '';
|
||||
@@ -185,23 +185,23 @@ export const prepareDraftRecord = ({action, database, value}: DataFactoryArgs) =
|
||||
database,
|
||||
tableName: DRAFT,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* preparePostsInChannelRecord: Prepares record of entity 'POSTS_IN_CHANNEL' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformPostsInChannelRecord: Prepares a record of the SERVER database 'PostsInChannel' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const preparePostsInChannelRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformPostsInChannelRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawPostsInChannel;
|
||||
const record = value.record as PostsInChannel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (postsInChannel: PostsInChannel) => {
|
||||
const fieldsMapper = (postsInChannel: PostsInChannel) => {
|
||||
postsInChannel._raw.id = isCreateAction ? postsInChannel.id : record.id;
|
||||
postsInChannel.channelId = raw.channel_id;
|
||||
postsInChannel.earliest = raw.earliest;
|
||||
@@ -213,6 +213,6 @@ export const preparePostsInChannelRecord = ({action, database, value}: DataFacto
|
||||
database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
@@ -2,24 +2,24 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
prepareMyTeamRecord,
|
||||
prepareSlashCommandRecord,
|
||||
prepareTeamChannelHistoryRecord,
|
||||
prepareTeamMembershipRecord,
|
||||
prepareTeamRecord,
|
||||
prepareTeamSearchHistoryRecord,
|
||||
} from '@database/operator/prepareRecords/team';
|
||||
transformMyTeamRecord,
|
||||
transformSlashCommandRecord,
|
||||
transformTeamChannelHistoryRecord,
|
||||
transformTeamMembershipRecord,
|
||||
transformTeamRecord,
|
||||
transformTeamSearchHistoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/team';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** TEAM Prepare Records Test ***', () => {
|
||||
it('=> prepareSlashCommandRecord: should return an array of type SlashCommand', async () => {
|
||||
it('=> transformSlashCommandRecord: should return an array of type SlashCommand', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareSlashCommandRecord({
|
||||
const preparedRecords = await transformSlashCommandRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -50,13 +50,13 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('SlashCommand');
|
||||
});
|
||||
|
||||
it('=> prepareMyTeamRecord: should return an array of type MyTeam', async () => {
|
||||
it('=> transformMyTeamRecord: should return an array of type MyTeam', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareMyTeamRecord({
|
||||
const preparedRecords = await transformMyTeamRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -74,13 +74,13 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('MyTeam');
|
||||
});
|
||||
|
||||
it('=> prepareTeamRecord: should return an array of type Team', async () => {
|
||||
it('=> transformTeamRecord: should return an array of type Team', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareTeamRecord({
|
||||
const preparedRecords = await transformTeamRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -110,13 +110,13 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Team');
|
||||
});
|
||||
|
||||
it('=> prepareTeamChannelHistoryRecord: should return an array of type Team', async () => {
|
||||
it('=> transformTeamChannelHistoryRecord: should return an array of type Team', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareTeamChannelHistoryRecord({
|
||||
const preparedRecords = await transformTeamChannelHistoryRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -132,13 +132,13 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('TeamChannelHistory');
|
||||
});
|
||||
|
||||
it('=> prepareTeamSearchHistoryRecord: should return an array of type TeamSearchHistory', async () => {
|
||||
it('=> transformTeamSearchHistoryRecord: should return an array of type TeamSearchHistory', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareTeamSearchHistoryRecord({
|
||||
const preparedRecords = await transformTeamSearchHistoryRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -156,13 +156,13 @@ describe('*** TEAM Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('TeamSearchHistory');
|
||||
});
|
||||
|
||||
it('=> prepareTeamMembershipRecord: should return an array of type TeamMembership', async () => {
|
||||
it('=> transformTeamMembershipRecord: should return an array of type TeamMembership', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'team_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareTeamMembershipRecord({
|
||||
const preparedRecords = await transformTeamMembershipRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
// See LICENSE.txt for license information.
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import {
|
||||
DataFactoryArgs,
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import type {
|
||||
TransformerArgs,
|
||||
RawMyTeam,
|
||||
RawSlashCommand,
|
||||
RawTeam,
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
RawTeamSearchHistory,
|
||||
} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import MyTeam from '@typings/database/my_team';
|
||||
import SlashCommand from '@typings/database/slash_command';
|
||||
import Team from '@typings/database/team';
|
||||
import TeamChannelHistory from '@typings/database/team_channel_history';
|
||||
import TeamMembership from '@typings/database/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/team_search_history';
|
||||
import MyTeam from '@typings/database/models/servers/my_team';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import TeamChannelHistory from '@typings/database/models/servers/team_channel_history';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/models/servers/team_search_history';
|
||||
|
||||
const {
|
||||
MY_TEAM,
|
||||
@@ -31,19 +31,19 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* preparePreferenceRecord: Prepares record of entity 'TEAM_MEMBERSHIP' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformTeamMembershipRecord: Prepares a record of the SERVER database 'TeamMembership' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareTeamMembershipRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformTeamMembershipRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawTeamMembership;
|
||||
const record = value.record as TeamMembership;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (teamMembership: TeamMembership) => {
|
||||
const fieldsMapper = (teamMembership: TeamMembership) => {
|
||||
teamMembership._raw.id = isCreateAction ? (raw?.id ?? teamMembership.id) : record.id;
|
||||
teamMembership.teamId = raw.team_id;
|
||||
teamMembership.userId = raw.user_id;
|
||||
@@ -54,24 +54,24 @@ export const prepareTeamMembershipRecord = ({action, database, value}: DataFacto
|
||||
database,
|
||||
tableName: TEAM_MEMBERSHIP,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareTeamRecord: Prepares record of entity 'TEAM' from the SERVER database for update or create actions.
|
||||
* transformTeamRecord: Prepares a record of the SERVER database 'Team' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareTeamRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformTeamRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawTeam;
|
||||
const record = value.record as Team;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (team: Team) => {
|
||||
const fieldsMapper = (team: Team) => {
|
||||
team._raw.id = isCreateAction ? (raw?.id ?? team.id) : record.id;
|
||||
team.isAllowOpenInvite = raw.allow_open_invite;
|
||||
team.description = raw.description;
|
||||
@@ -89,23 +89,23 @@ export const prepareTeamRecord = ({action, database, value}: DataFactoryArgs) =>
|
||||
database,
|
||||
tableName: TEAM,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareTeamChannelHistoryRecord: Prepares record of entity 'TEAM_CHANNEL_HISTORY' from the SERVER database for update or create actions.
|
||||
* transformTeamChannelHistoryRecord: Prepares a record of the SERVER database 'TeamChannelHistory' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareTeamChannelHistoryRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformTeamChannelHistoryRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawTeamChannelHistory;
|
||||
const record = value.record as TeamChannelHistory;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (teamChannelHistory: TeamChannelHistory) => {
|
||||
const fieldsMapper = (teamChannelHistory: TeamChannelHistory) => {
|
||||
teamChannelHistory._raw.id = isCreateAction ? (teamChannelHistory.id) : record.id;
|
||||
teamChannelHistory.teamId = raw.team_id;
|
||||
teamChannelHistory.channelIds = raw.channel_ids;
|
||||
@@ -116,23 +116,23 @@ export const prepareTeamChannelHistoryRecord = ({action, database, value}: DataF
|
||||
database,
|
||||
tableName: TEAM_CHANNEL_HISTORY,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareTeamSearchHistoryRecord: Prepares record of entity 'TEAM_SEARCH_HISTORY' from the SERVER database for update or create actions.
|
||||
* transformTeamSearchHistoryRecord: Prepares a record of the SERVER database 'TeamSearchHistory' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareTeamSearchHistoryRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformTeamSearchHistoryRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawTeamSearchHistory;
|
||||
const record = value.record as TeamSearchHistory;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (teamSearchHistory: TeamSearchHistory) => {
|
||||
const fieldsMapper = (teamSearchHistory: TeamSearchHistory) => {
|
||||
teamSearchHistory._raw.id = isCreateAction ? (teamSearchHistory.id) : record.id;
|
||||
teamSearchHistory.createdAt = raw.created_at;
|
||||
teamSearchHistory.displayTerm = raw.display_term;
|
||||
@@ -145,24 +145,24 @@ export const prepareTeamSearchHistoryRecord = ({action, database, value}: DataFa
|
||||
database,
|
||||
tableName: TEAM_SEARCH_HISTORY,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareSlashCommandRecord: Prepares record of entity 'SLASH_COMMAND' from the SERVER database for update or create actions.
|
||||
* transformSlashCommandRecord: Prepares a record of the SERVER database 'SlashCommand' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareSlashCommandRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformSlashCommandRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawSlashCommand;
|
||||
const record = value.record as SlashCommand;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (slashCommand: SlashCommand) => {
|
||||
const fieldsMapper = (slashCommand: SlashCommand) => {
|
||||
slashCommand._raw.id = isCreateAction ? (raw?.id ?? slashCommand.id) : record.id;
|
||||
slashCommand.isAutoComplete = raw.auto_complete;
|
||||
slashCommand.description = raw.description;
|
||||
@@ -180,23 +180,23 @@ export const prepareSlashCommandRecord = ({action, database, value}: DataFactory
|
||||
database,
|
||||
tableName: SLASH_COMMAND,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareMyTeamRecord: Prepares record of entity 'MY_TEAM' from the SERVER database for update or create actions.
|
||||
* transformMyTeamRecord: Prepares a record of the SERVER database 'MyTeam' table for update or create actions.
|
||||
* @param {DataFactory} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareMyTeamRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformMyTeamRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawMyTeam;
|
||||
const record = value.record as MyTeam;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
const generator = (myTeam: MyTeam) => {
|
||||
const fieldsMapper = (myTeam: MyTeam) => {
|
||||
myTeam._raw.id = isCreateAction ? myTeam.id : record.id;
|
||||
myTeam.teamId = raw.team_id;
|
||||
myTeam.roles = raw.roles;
|
||||
@@ -209,6 +209,6 @@ export const prepareMyTeamRecord = ({action, database, value}: DataFactoryArgs)
|
||||
database,
|
||||
tableName: MY_TEAM,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
@@ -1,24 +1,24 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {
|
||||
prepareChannelMembershipRecord,
|
||||
preparePreferenceRecord,
|
||||
prepareReactionRecord,
|
||||
prepareUserRecord,
|
||||
} from '@database/operator/prepareRecords/user';
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformReactionRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
|
||||
// See LICENSE.txt for license information.
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** USER Prepare Records Test ***', () => {
|
||||
it('=> prepareChannelMembershipRecord: should return an array of type ChannelMembership', async () => {
|
||||
it('=> transformChannelMembershipRecord: should return an array of type ChannelMembership', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'user_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareChannelMembershipRecord({
|
||||
const preparedRecords = await transformChannelMembershipRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -50,13 +50,13 @@ describe('*** USER Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('ChannelMembership');
|
||||
});
|
||||
|
||||
it('=> preparePreferenceRecord: should return an array of type Preference', async () => {
|
||||
it('=> transformPreferenceRecord: should return an array of type Preference', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'user_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await preparePreferenceRecord({
|
||||
const preparedRecords = await transformPreferenceRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -69,13 +69,13 @@ describe('*** USER Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Preference');
|
||||
});
|
||||
|
||||
it('=> prepareReactionRecord: should return an array of type Reaction', async () => {
|
||||
it('=> transformReactionRecord: should return an array of type Reaction', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'user_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareReactionRecord({
|
||||
const preparedRecords = await transformReactionRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -96,13 +96,13 @@ describe('*** USER Prepare Records Test ***', () => {
|
||||
expect(preparedRecords!.collection.modelClass.name).toBe('Reaction');
|
||||
});
|
||||
|
||||
it('=> prepareUserRecord: should return an array of type User', async () => {
|
||||
it('=> transformUserRecord: should return an array of type User', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'user_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await prepareUserRecord({
|
||||
const preparedRecords = await transformUserRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
@@ -143,7 +143,7 @@ describe('*** USER Prepare Records Test ***', () => {
|
||||
timezone: {
|
||||
automaticTimezone: 'Indian/Mauritius',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: true,
|
||||
useAutomaticTimezone: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2,13 +2,13 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/prepareRecords/index';
|
||||
import ChannelMembership from '@typings/database/channel_membership';
|
||||
import {DataFactoryArgs, RawChannelMembership, RawPreference, RawReaction, RawUser} from '@typings/database/database';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import {TransformerArgs, RawChannelMembership, RawPreference, RawReaction, RawUser} from '@typings/database/database';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
import Preference from '@typings/database/preference';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import User from '@typings/database/user';
|
||||
import Preference from '@typings/database/models/servers/preference';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CHANNEL_MEMBERSHIP,
|
||||
@@ -18,19 +18,19 @@ const {
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* prepareReactionRecord: Prepares record of entity 'REACTION' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformReactionRecord: Prepares a record of the SERVER database 'Reaction' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareReactionRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformReactionRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawReaction;
|
||||
const record = value.record as Reaction;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// id of reaction comes from server response
|
||||
const generator = (reaction: Reaction) => {
|
||||
const fieldsMapper = (reaction: Reaction) => {
|
||||
reaction._raw.id = isCreateAction ? (raw?.id ?? reaction.id) : record.id;
|
||||
reaction.userId = raw.user_id;
|
||||
reaction.postId = raw.post_id;
|
||||
@@ -43,24 +43,24 @@ export const prepareReactionRecord = ({action, database, value}: DataFactoryArgs
|
||||
database,
|
||||
tableName: REACTION,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareUserRecord: Prepares record of entity 'USER' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformUserRecord: Prepares a record of the SERVER database 'User' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareUserRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformUserRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawUser;
|
||||
const record = value.record as User;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// id of user comes from server response
|
||||
const generator = (user: User) => {
|
||||
const fieldsMapper = (user: User) => {
|
||||
user._raw.id = isCreateAction ? (raw?.id ?? user.id) : record.id;
|
||||
user.authService = raw.auth_service;
|
||||
user.deleteAt = raw.delete_at;
|
||||
@@ -86,24 +86,24 @@ export const prepareUserRecord = ({action, database, value}: DataFactoryArgs) =>
|
||||
database,
|
||||
tableName: USER,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* preparePreferenceRecord: Prepares record of entity 'PREFERENCE' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformPreferenceRecord: Prepares a record of the SERVER database 'Preference' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const preparePreferenceRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformPreferenceRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawPreference;
|
||||
const record = value.record as Preference;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// id of preference comes from server response
|
||||
const generator = (preference: Preference) => {
|
||||
const fieldsMapper = (preference: Preference) => {
|
||||
preference._raw.id = isCreateAction ? preference.id : record.id;
|
||||
preference.category = raw.category;
|
||||
preference.name = raw.name;
|
||||
@@ -116,24 +116,24 @@ export const preparePreferenceRecord = ({action, database, value}: DataFactoryAr
|
||||
database,
|
||||
tableName: PREFERENCE,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* prepareChannelMembershipRecord: Prepares record of entity 'CHANNEL_MEMBERSHIP' from the SERVER database for update or create actions.
|
||||
* @param {DataFactoryArgs} operator
|
||||
* transformChannelMembershipRecord: Prepares a record of the SERVER database 'ChannelMembership' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<Model>}
|
||||
*/
|
||||
export const prepareChannelMembershipRecord = ({action, database, value}: DataFactoryArgs) => {
|
||||
export const transformChannelMembershipRecord = ({action, database, value}: TransformerArgs) => {
|
||||
const raw = value.raw as RawChannelMembership;
|
||||
const record = value.record as ChannelMembership;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
|
||||
const generator = (channelMember: ChannelMembership) => {
|
||||
const fieldsMapper = (channelMember: ChannelMembership) => {
|
||||
channelMember._raw.id = isCreateAction ? (raw?.id ?? channelMember.id) : record.id;
|
||||
channelMember.channelId = raw.channel_id;
|
||||
channelMember.userId = raw.user_id;
|
||||
@@ -144,6 +144,6 @@ export const prepareChannelMembershipRecord = ({action, database, value}: DataFa
|
||||
database,
|
||||
tableName: CHANNEL_MEMBERSHIP,
|
||||
value,
|
||||
generator,
|
||||
fieldsMapper,
|
||||
});
|
||||
};
|
||||
@@ -4,25 +4,20 @@
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
// NOTE: uncomment the below line if you are manually testing the database
|
||||
jest.mock('@database/manager');
|
||||
|
||||
export const createTestConnection = async ({databaseName = 'db_name', setActive = false}) => {
|
||||
const serverUrl = 'https://appv2.mattermost.com';
|
||||
const databaseClient = new DatabaseManager();
|
||||
const database = await databaseClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
await DatabaseManager.init([]);
|
||||
const server = await DatabaseManager.createServerDatabase({
|
||||
config: {
|
||||
dbName: databaseName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
|
||||
if (setActive) {
|
||||
await databaseClient.setActiveServerDatabase(serverUrl);
|
||||
if (setActive && server) {
|
||||
await DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
}
|
||||
|
||||
return database;
|
||||
return server?.database;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Channel from '@typings/database/channel';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import {
|
||||
IdenticalRecordArgs,
|
||||
RangeOfValueArgs,
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
RecordPair,
|
||||
RetrieveRecordsArgs,
|
||||
} from '@typings/database/database';
|
||||
import Post from '@typings/database/post';
|
||||
import SlashCommand from '@typings/database/slash_command';
|
||||
import Team from '@typings/database/team';
|
||||
import User from '@typings/database/user';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {CHANNEL, POST, SLASH_COMMAND, TEAM, USER} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* getValidRecordsForUpdate: Database Operations on some entities are expensive. As such, we would like to operate if and only if we are
|
||||
* getValidRecordsForUpdate: Database Operations on some tables are expensive. As such, we would like to operate if and only if we are
|
||||
* 100% sure that the records are actually different from what we already have in the database.
|
||||
* @param {IdenticalRecordArgs} identicalRecord
|
||||
* @param {string} identicalRecord.tableName
|
||||
|
||||
@@ -4,7 +4,7 @@ import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {RecordPair, SanitizeReactionsArgs} from '@typings/database/database';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
|
||||
const {REACTION} = MM_TABLES.SERVER;
|
||||
|
||||
@@ -24,7 +24,7 @@ export const sanitizeReactions = async ({database, post_id, rawReactions}: Sanit
|
||||
query(Q.where('post_id', post_id)).
|
||||
fetch()) as Reaction[];
|
||||
|
||||
// similarObjects: Contains objects that are in both the RawReaction array and in the Reaction entity
|
||||
// similarObjects: Contains objects that are in both the RawReaction array and in the Reaction table
|
||||
const similarObjects: Reaction[] = [];
|
||||
|
||||
const createReactions: RecordPair[] = [];
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Operator from '@database/operator';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {createPostsChain, sanitizePosts} from '@database/operator/utils/post';
|
||||
import {sanitizeReactions} from '@database/operator/utils/reaction';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
import {RawPost} from '@typings/database/database';
|
||||
import Reaction from '@typings/database/reaction';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
|
||||
import {mockedPosts, mockedReactions} from './mock';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
describe('DataOperator: Utils tests', () => {
|
||||
it('=> sanitizePosts: should filter between ordered and unordered posts', () => {
|
||||
const {postsOrdered, postsUnordered} = sanitizePosts({
|
||||
@@ -71,22 +67,15 @@ describe('DataOperator: Utils tests', () => {
|
||||
it('=> sanitizeReactions: should triage between reactions that needs creation/deletion and emojis to be created', async () => {
|
||||
const dbName = 'server_schema_connection';
|
||||
const serverUrl = 'https://appv2.mattermost.com';
|
||||
const databaseManagerClient = new DatabaseManager();
|
||||
const database = await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
const server = await DatabaseManager.createServerDatabase({
|
||||
config: {
|
||||
dbName,
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
});
|
||||
await databaseManagerClient.setActiveServerDatabase(serverUrl);
|
||||
|
||||
const operatorClient = new Operator(database!);
|
||||
|
||||
// we commit one Reaction to our database
|
||||
const prepareRecords = await operatorClient.handleReactions({
|
||||
const prepareRecords = await server?.operator.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'beqkgo4wzbn98kjzjgc1p5n91o',
|
||||
@@ -102,8 +91,8 @@ describe('DataOperator: Utils tests', () => {
|
||||
|
||||
// Jest in not using the same database instance amongst the Singletons; hence, we are creating the reaction record here
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
await database!.action(async () => {
|
||||
await database!.batch(...prepareRecords);
|
||||
await server?.database!.action(async () => {
|
||||
await server.database!.batch(...prepareRecords);
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -111,7 +100,7 @@ describe('DataOperator: Utils tests', () => {
|
||||
createEmojis,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database: database!,
|
||||
database: server!.database,
|
||||
post_id: '8ww8kb1dbpf59fu4d5xhu5nf5w',
|
||||
rawReactions: mockedReactions,
|
||||
});
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Operator from '@database/operator/index';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import DatabaseConnectionException from '@database/exceptions/database_connection_exception';
|
||||
|
||||
export const createDataOperator = async (serverUrl: string) => {
|
||||
const databaseManagerClient = new DatabaseManager();
|
||||
|
||||
const connection = await databaseManagerClient.getDatabaseConnection({serverUrl, setAsActiveDatabase: false});
|
||||
|
||||
if (connection) {
|
||||
const operator = new Operator(connection);
|
||||
return operator;
|
||||
}
|
||||
throw new DatabaseConnectionException(
|
||||
`No database has been registered with this url: ${serverUrl}`,
|
||||
);
|
||||
};
|
||||
@@ -1,339 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {createDataOperator} from '@database/operator/wrapper/index';
|
||||
import {DatabaseType} from '@typings/database/enums';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('*** DataOperator Wrapper ***', () => {
|
||||
let databaseManagerClient: DatabaseManager;
|
||||
|
||||
beforeAll(async () => {
|
||||
databaseManagerClient = new DatabaseManager();
|
||||
});
|
||||
|
||||
it('=> wrapper should return an instance of DataOperator ', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const serverUrl = 'https://wrapper.mattermost.com';
|
||||
|
||||
// first we create the connection and save it into default database
|
||||
await databaseManagerClient.createDatabaseConnection({
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'community mattermost',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl,
|
||||
},
|
||||
shouldAddToDefaultDatabase: true,
|
||||
});
|
||||
|
||||
const dataOperator = await createDataOperator(serverUrl);
|
||||
|
||||
expect(dataOperator).toBeTruthy();
|
||||
});
|
||||
|
||||
it('=> wrapper to handlePosts [OTHER DATABASE]: should write to Post and its sub-child entities', async () => {
|
||||
expect.assertions(12);
|
||||
|
||||
const posts = [
|
||||
{
|
||||
id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
create_at: 1596032651747,
|
||||
update_at: 1596032651747,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: 'q3mzxua9zjfczqakxdkowc6u6yy',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '',
|
||||
parent_id: 'ps81iqbddesfby8jayz7owg4yypoo',
|
||||
original_id: '',
|
||||
message: "I'll second these kudos! Thanks m!",
|
||||
type: '',
|
||||
props: {},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 4,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {
|
||||
images: {
|
||||
'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4': {
|
||||
width: 400,
|
||||
height: 400,
|
||||
format: 'png',
|
||||
frame_count: 0,
|
||||
},
|
||||
},
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
update_at: 1608252965442,
|
||||
delete_at: 0,
|
||||
},
|
||||
],
|
||||
embeds: [
|
||||
{
|
||||
type: 'opengraph',
|
||||
url:
|
||||
'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
data: {
|
||||
type: 'object',
|
||||
url:
|
||||
'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
title: 'mickmister/mattermost-plugin-default-theme',
|
||||
description:
|
||||
'Contribute to mickmister/mattermost-plugin-default-theme development by creating an account on GitHub.',
|
||||
determiner: '',
|
||||
site_name: 'GitHub',
|
||||
locale: '',
|
||||
locales_alternate: null,
|
||||
images: [
|
||||
{
|
||||
url: '',
|
||||
secure_url:
|
||||
'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4',
|
||||
type: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
],
|
||||
audios: null,
|
||||
videos: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
emojis: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
create_at: 1502389307432,
|
||||
update_at: 1502389307432,
|
||||
delete_at: 0,
|
||||
creator_id: 'x6sdh1ok1tyd9f4dgq4ybw839a',
|
||||
name: 'thanks',
|
||||
},
|
||||
],
|
||||
files: [
|
||||
{
|
||||
id: 'f1oxe5rtepfs7n3zifb4sso7po',
|
||||
user_id: '89ertha8xpfsumpucqppy5knao',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
create_at: 1608270920357,
|
||||
update_at: 1608270920357,
|
||||
delete_at: 0,
|
||||
name: '4qtwrg.jpg',
|
||||
extension: 'jpg',
|
||||
size: 89208,
|
||||
mime_type: 'image/jpeg',
|
||||
width: 500,
|
||||
height: 656,
|
||||
has_preview_image: true,
|
||||
mini_preview:
|
||||
'/9j/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIABAAEAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AN/T/iZp+pX15FpUmnwLbXtpJpyy2sQLw8CcBXA+bksCDnHGOaf4W+P3xIshbQ6loB8RrbK11f3FpbBFW3ZwiFGHB2kr25BIOeCPPbX4S3407T7rTdDfxFNIpDyRaw9lsB4OECHGR15yO4GK6fRPhR4sGmSnxAs8NgchNOjvDPsjz8qSHA37cDk5JPPFdlOpTdPlcVt/Ku1lrvr17b67EPnjrH8/626H/9k=',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
create_at: 1596104683748,
|
||||
update_at: 1596104683748,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: 'hy5sq51sebfh58ktrce5ijtcwyy',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
parent_id: '',
|
||||
original_id: '',
|
||||
message: 'a added to the channel by j.',
|
||||
type: 'system_add_to_channel',
|
||||
props: {
|
||||
addedUserId: 'z89qsntet7bimd3xddfu7u9ncdaxc',
|
||||
addedUsername: 'a',
|
||||
userId: 'hy5sdfdfq51sebfh58ktrce5ijtcwy',
|
||||
username: 'j',
|
||||
},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 0,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
id: '3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
create_at: 1596277483749,
|
||||
update_at: 1596277483749,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: '44ud4m9tqwby3mphzzdwm7h31sr',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
parent_id: 'ps81iqbwesfby8jayz7owg4yypo',
|
||||
original_id: '',
|
||||
message: 'Great work M!',
|
||||
type: '',
|
||||
props: {},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 4,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {},
|
||||
},
|
||||
];
|
||||
|
||||
// create connection to other server in default db
|
||||
await databaseManagerClient.createDatabaseConnection({
|
||||
shouldAddToDefaultDatabase: true,
|
||||
configs: {
|
||||
actionsEnabled: true,
|
||||
dbName: 'other_server',
|
||||
dbType: DatabaseType.SERVER,
|
||||
serverUrl: 'https://appv1.mattermost.com',
|
||||
},
|
||||
});
|
||||
const dataOperator = await createDataOperator('https://appv1.mattermost.com');
|
||||
|
||||
const spyOnHandleReactions = jest.spyOn(dataOperator as any, 'handleReactions');
|
||||
const spyOnHandleFiles = jest.spyOn(dataOperator as any, 'handleFiles');
|
||||
const spyOnHandlePostMetadata = jest.spyOn(dataOperator as any, 'handlePostMetadata');
|
||||
const spyOnHandleCustomEmojis = jest.spyOn(dataOperator as any, 'handleIsolatedEntity');
|
||||
const spyOnHandlePostsInThread = jest.spyOn(dataOperator as any, 'handlePostsInThread');
|
||||
const spyOnHandlePostsInChannel = jest.spyOn(dataOperator as any, 'handlePostsInChannel');
|
||||
|
||||
// handlePosts will in turn call handlePostsInThread
|
||||
await dataOperator.handlePosts({
|
||||
orders: [
|
||||
'8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
'8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
'3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
],
|
||||
values: posts,
|
||||
previousPostId: '',
|
||||
});
|
||||
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledWith({
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
update_at: 1608252965442,
|
||||
delete_at: 0,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandleFiles).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleFiles).toHaveBeenCalledWith({
|
||||
files: [
|
||||
{
|
||||
id: 'f1oxe5rtepfs7n3zifb4sso7po',
|
||||
user_id: '89ertha8xpfsumpucqppy5knao',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
create_at: 1608270920357,
|
||||
update_at: 1608270920357,
|
||||
delete_at: 0,
|
||||
name: '4qtwrg.jpg',
|
||||
extension: 'jpg',
|
||||
size: 89208,
|
||||
mime_type: 'image/jpeg',
|
||||
width: 500,
|
||||
height: 656,
|
||||
has_preview_image: true,
|
||||
mini_preview:
|
||||
'/9j/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIABAAEAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AN/T/iZp+pX15FpUmnwLbXtpJpyy2sQLw8CcBXA+bksCDnHGOaf4W+P3xIshbQ6loB8RrbK11f3FpbBFW3ZwiFGHB2kr25BIOeCPPbX4S3407T7rTdDfxFNIpDyRaw9lsB4OECHGR15yO4GK6fRPhR4sGmSnxAs8NgchNOjvDPsjz8qSHA37cDk5JPPFdlOpTdPlcVt/Ku1lrvr17b67EPnjrH8/626H/9k=',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandlePostMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostMetadata).toHaveBeenCalledWith({
|
||||
embeds: [
|
||||
{
|
||||
embed: [
|
||||
{
|
||||
type: 'opengraph',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
data: {
|
||||
type: 'object',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
title: 'mickmister/mattermost-plugin-default-theme',
|
||||
description: 'Contribute to mickmister/mattermost-plugin-default-theme development by creating an account on GitHub.',
|
||||
determiner: '',
|
||||
site_name: 'GitHub',
|
||||
locale: '',
|
||||
locales_alternate: null,
|
||||
images: [
|
||||
{
|
||||
url: '',
|
||||
secure_url: 'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4',
|
||||
type: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
],
|
||||
audios: null,
|
||||
videos: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
postId: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
},
|
||||
],
|
||||
images: [
|
||||
{
|
||||
images: {
|
||||
'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4': {
|
||||
width: 400,
|
||||
height: 400,
|
||||
format: 'png',
|
||||
frame_count: 0,
|
||||
},
|
||||
},
|
||||
postId: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledWith({
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
values: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
create_at: 1502389307432,
|
||||
update_at: 1502389307432,
|
||||
delete_at: 0,
|
||||
creator_id: 'x6sdh1ok1tyd9f4dgq4ybw839a',
|
||||
name: 'thanks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledWith([
|
||||
{earliest: 1596032651747, post_id: '8swgtrrdiff89jnsiwiip3y1eoe'},
|
||||
]);
|
||||
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledWith(posts.slice(0, 3));
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,13 @@
|
||||
import {AppSchema, appSchema, tableSchema} from '@nozbe/watermelondb';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {APP, GLOBAL, SERVERS} = MM_TABLES.DEFAULT;
|
||||
const {INFO, GLOBAL, SERVERS} = MM_TABLES.APP;
|
||||
|
||||
export const defaultSchema: AppSchema = appSchema({
|
||||
export const schema: AppSchema = appSchema({
|
||||
version: 1,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: APP,
|
||||
name: INFO,
|
||||
columns: [
|
||||
{name: 'build_number', type: 'string'},
|
||||
{name: 'created_at', type: 'number'},
|
||||
@@ -5,10 +5,10 @@ import {tableSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {APP} = MM_TABLES.DEFAULT;
|
||||
const {INFO} = MM_TABLES.APP;
|
||||
|
||||
export default tableSchema({
|
||||
name: APP,
|
||||
name: INFO,
|
||||
columns: [
|
||||
{name: 'build_number', type: 'string'},
|
||||
{name: 'created_at', type: 'number'},
|
||||
@@ -5,7 +5,7 @@ import {tableSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {GLOBAL} = MM_TABLES.DEFAULT;
|
||||
const {GLOBAL} = MM_TABLES.APP;
|
||||
|
||||
export default tableSchema({
|
||||
name: GLOBAL,
|
||||
@@ -5,7 +5,7 @@ import {tableSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
const {SERVERS} = MM_TABLES.DEFAULT;
|
||||
const {SERVERS} = MM_TABLES.APP;
|
||||
|
||||
export default tableSchema({
|
||||
name: SERVERS,
|
||||
@@ -3,17 +3,17 @@
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import {defaultSchema} from './index';
|
||||
import {schema} from './index';
|
||||
|
||||
const {APP, GLOBAL, SERVERS} = MM_TABLES.DEFAULT;
|
||||
const {INFO, GLOBAL, SERVERS} = MM_TABLES.APP;
|
||||
|
||||
describe('*** Test schema for DEFAULT database ***', () => {
|
||||
it('=> The DEFAULT SCHEMA should strictly match', () => {
|
||||
expect(defaultSchema).toEqual({
|
||||
expect(schema).toEqual({
|
||||
version: 1,
|
||||
tables: {
|
||||
[APP]: {
|
||||
name: APP,
|
||||
[INFO]: {
|
||||
name: INFO,
|
||||
columns: {
|
||||
build_number: {name: 'build_number', type: 'string'},
|
||||
created_at: {name: 'created_at', type: 'number'},
|
||||
@@ -19,22 +19,19 @@ const ASYNC_STORAGE_CURRENT_SERVER_KEY = '@currentServerUrl';
|
||||
// At some point we can remove this function and rely solely on
|
||||
// the database manager's `getActiveServerUrl`.
|
||||
export const getActiveServerUrl = async () => {
|
||||
let serverUrl: string | null | undefined;
|
||||
|
||||
const databaseManager = new DatabaseManager();
|
||||
serverUrl = await databaseManager.getActiveServerUrl(); // TODO: need funciton to get active server url
|
||||
let serverUrl = await DatabaseManager.getActiveServerUrl();
|
||||
if (!serverUrl) {
|
||||
// If upgrading from non-Gekidou, the server URL might be in
|
||||
// AsyncStorage. If so, retrieve the server URL, create a DB for it,
|
||||
// then delete the AsyncStorage item.
|
||||
serverUrl = await AsyncStorage.getItem(ASYNC_STORAGE_CURRENT_SERVER_KEY);
|
||||
if (serverUrl) {
|
||||
databaseManager.setActiveServerDatabase(serverUrl);
|
||||
DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
AsyncStorage.removeItem(ASYNC_STORAGE_CURRENT_SERVER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
return serverUrl;
|
||||
return serverUrl || undefined;
|
||||
};
|
||||
|
||||
export const setServerCredentials = (serverUrl: string, userId: string, token: string) => {
|
||||
@@ -59,15 +56,6 @@ export const setServerCredentials = (serverUrl: string, userId: string, token: s
|
||||
}
|
||||
};
|
||||
|
||||
export const getActiveServerCredentials = async () => {
|
||||
const serverUrl = await getActiveServerUrl();
|
||||
if (serverUrl) {
|
||||
return getServerCredentials(serverUrl);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const removeServerCredentials = async (serverUrl: string) => {
|
||||
// TODO: invalidate client and remove tokens
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Linking} from 'react-native';
|
||||
import {Notifications} from 'react-native-notifications';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {getActiveServerCredentials, getServerCredentials} from '@init/credentials';
|
||||
import {getActiveServerUrl, getServerCredentials} from '@init/credentials';
|
||||
import {goToScreen, resetToChannel, resetToSelectServer} from '@screens/navigation';
|
||||
import {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkType, DeepLinkWithData, LaunchProps, LaunchType} from '@typings/launch';
|
||||
import {parseDeepLink} from '@utils/url';
|
||||
@@ -52,11 +52,14 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => {
|
||||
}
|
||||
}
|
||||
|
||||
const credentials = serverUrl ? await getServerCredentials(serverUrl) : await getActiveServerCredentials();
|
||||
serverUrl = await getActiveServerUrl();
|
||||
if (serverUrl) {
|
||||
const credentials = await getServerCredentials(serverUrl);
|
||||
|
||||
if (credentials) {
|
||||
launchToChannel(props, resetNavigation);
|
||||
return;
|
||||
if (credentials) {
|
||||
launchToChannel({...props, serverUrl}, resetNavigation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
launchToServer(props, resetNavigation);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
|
||||
import {getActiveServerDatabase} from '@utils/database';
|
||||
import {AppState, AppStateStatus, DeviceEventEmitter, EmitterSubscription, Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {
|
||||
@@ -17,11 +16,10 @@ import {
|
||||
} from 'react-native-notifications';
|
||||
|
||||
import {Device, Navigation, View} from '@constants';
|
||||
import Operator from '@database/operator';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getLaunchPropsFromNotification, relaunchApp} from '@init/launch';
|
||||
import NativeNotifications from '@notifications';
|
||||
import {showOverlay} from '@screens/navigation';
|
||||
import {IsolatedEntities} from '@typings/database/enums';
|
||||
|
||||
const CATEGORY = 'CAN_REPLY';
|
||||
const REPLY_ACTION = 'REPLY_ACTION';
|
||||
@@ -199,17 +197,14 @@ class PushNotifications {
|
||||
prefix = Device.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
}
|
||||
|
||||
const {activeServerDatabase, error} = await getActiveServerDatabase();
|
||||
const operator = DatabaseManager.appDatabase?.operator;
|
||||
|
||||
if (!activeServerDatabase) {
|
||||
return {error};
|
||||
if (!operator) {
|
||||
return {error: 'No App database found'};
|
||||
}
|
||||
|
||||
const operator = new Operator(activeServerDatabase);
|
||||
|
||||
operator.handleIsolatedEntity({
|
||||
tableName: IsolatedEntities.GLOBAL,
|
||||
values: [{name: 'deviceToken', value: `${prefix}:${deviceToken}`}],
|
||||
operator.handleGlobal({
|
||||
global: [{name: 'deviceToken', value: `${prefix}:${deviceToken}`}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
|
||||
14
app/queries/app/global.ts
Normal file
14
app/queries/app/global.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import type Global from '@typings/database/models/app/global';
|
||||
|
||||
const {APP: {GLOBAL}} = MM_TABLES;
|
||||
|
||||
export const getDeviceToken = async (appDatabase: Database) => {
|
||||
const tokens = (await appDatabase.collections.get(GLOBAL).query(Q.where('name', 'deviceToken')).fetch()) as Global[];
|
||||
return tokens?.[0]?.value ?? '';
|
||||
};
|
||||
27
app/queries/app/servers.ts
Normal file
27
app/queries/app/servers.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import type Servers from '@typings/database/models/app/servers';
|
||||
|
||||
const {APP: {SERVERS}} = MM_TABLES;
|
||||
|
||||
export const getServer = async (appDatabase: Database, serverUrl: string) => {
|
||||
const servers = (await appDatabase.collections.get(SERVERS).query(Q.where('url', serverUrl)).fetch()) as Servers[];
|
||||
return servers?.[0];
|
||||
};
|
||||
|
||||
export const getAllServers = async (appDatabse: Database) => {
|
||||
return (await appDatabse.collections.get(MM_TABLES.APP.SERVERS).query().fetch()) as Servers[];
|
||||
};
|
||||
|
||||
export const getActiveServer = async (appDatabse: Database) => {
|
||||
try {
|
||||
const servers = await getAllServers(appDatabse);
|
||||
return servers.reduce((a, b) => (b.lastActiveAt > a.lastActiveAt ? b : a));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
import Global from '@typings/database/global';
|
||||
|
||||
const {DEFAULT: {GLOBAL}} = MM_TABLES;
|
||||
|
||||
export const getDeviceToken = async (defaultDatabase: Database) => {
|
||||
const tokens = (await defaultDatabase.collections.get(GLOBAL).query(Q.where('name', 'deviceToken')).fetch()) as Global[];
|
||||
return tokens?.[0]?.value ?? '';
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import Role from '@typings/database/role';
|
||||
import Role from '@typings/database/models/servers/role';
|
||||
|
||||
const {SERVER: {ROLE}} = MM_TABLES;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user