Chore: Migrate Database to Typescript (#3580)

Co-authored-by: Gleidson Daniel Silva <gleidson10daniel@hotmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-02-01 10:39:09 -03:00 committed by GitHub
parent 761f4d1e2c
commit f88bdfb97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 280 additions and 145 deletions

View File

@ -1,16 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import { Observable, Subscription } from 'rxjs';
import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login';
import { TSubscriptionModel, TUserModel } from '../../definitions';
import Avatar from './Avatar';
import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean;
private subscription: any;
private subscription?: Subscription;
static defaultProps = {
text: '',
@ -59,15 +61,17 @@ class AvatarContainer extends React.Component<IAvatar, any> {
record = user;
} else {
const { rid } = this.props;
record = await subsCollection.find(rid);
if (rid) {
record = await subsCollection.find(rid);
}
}
} catch {
// Record not found
}
if (record) {
const observable = record.observe();
this.subscription = observable.subscribe((r: any) => {
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
this.subscription = observable.subscribe(r => {
const { avatarETag } = r;
if (this.mounted) {
this.setState({ avatarETag });

View File

@ -36,7 +36,7 @@ interface IEmojiPickerProps {
}
interface IEmojiPickerState {
frequentlyUsed: [];
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
customEmojis: any;
show: boolean;
width: number | null;
@ -114,7 +114,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
// Do nothing
}
await db.action(async () => {
await db.write(async () => {
if (freqEmojiRecord) {
await freqEmojiRecord.update((f: any) => {
f.count += 1;
@ -132,8 +132,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
updateFrequentlyUsed = async () => {
const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
frequentlyUsed = frequentlyUsed.map((item: IEmoji) => {
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}

View File

@ -10,6 +10,7 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles';
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
import { IEmoji } from '../EmojiPicker/interfaces';
interface IHeader {
@ -90,14 +91,14 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
));
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
const [items, setItems] = useState([]);
const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
const { width, height }: any = useDimensions();
const setEmojis = async () => {
try {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis = await freqEmojiCollection.query().fetch();
let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;

View File

@ -15,6 +15,7 @@ import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header';
import events from '../../utils/log/events';
import { TMessageModel } from '../../definitions/IMessage';
interface IMessageActions {
room: {
@ -182,9 +183,9 @@ const MessageActions = React.memo(
if (result.success) {
const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid);
await db.action(async () => {
await db.write(async () => {
try {
await subRecord.update((sub: any) => (sub.lastOpen = ts));
await subRecord.update(sub => (sub.lastOpen = ts));
} catch {
// do nothing
}
@ -269,11 +270,11 @@ const MessageActions = React.memo(
}
};
const handleToggleTranslation = async (message: any) => {
const handleToggleTranslation = async (message: TMessageModel) => {
try {
const db = database.active;
await db.action(async () => {
await message.update((m: any) => {
await db.write(async () => {
await message.update(m => {
m.autoTranslate = !m.autoTranslate;
m._updatedAt = new Date();
});
@ -320,7 +321,7 @@ const MessageActions = React.memo(
});
};
const getOptions = (message: any) => {
const getOptions = (message: TMessageModel) => {
let options: any = [];
// Reply
@ -446,7 +447,7 @@ const MessageActions = React.memo(
return options;
};
const showMessageActions = async (message: any) => {
const showMessageActions = async (message: TMessageModel) => {
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
await getPermissions();
showActionSheet({

View File

@ -36,7 +36,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
try {
// Find the thread header and update it
const msg = await msgCollection.find(tmid);
if (msg.tcount <= 1) {
if (msg?.tcount && msg.tcount <= 1) {
deleteBatch.push(
msg.prepareUpdate((m: any) => {
m.tcount = null;
@ -62,7 +62,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
// Do nothing: message not found
}
}
await db.action(async () => {
await db.write(async () => {
await db.batch(...deleteBatch);
});
} catch (e) {

View File

@ -52,8 +52,9 @@ interface IThreadDetails {
}
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => {
let { tcount } = item;
if (tcount! >= 1000) {
let tcount: number | string = item?.tcount ?? 0;
if (tcount >= 1000) {
tcount = '+999';
}

View File

@ -182,7 +182,7 @@ const Fields = React.memo(
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
{/* @ts-ignore*/}
<Markdown
msg={field.value!}
msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}

View File

@ -101,7 +101,7 @@ export interface IMessageThread {
msg: string;
tcount: number;
theme: string;
tlm: string;
tlm: Date;
isThreadRoom: boolean;
id: string;
}

View File

@ -1,3 +1,4 @@
import { TMessageModel } from '../../definitions/IMessage';
import I18n from '../../i18n';
import { DISCUSSION } from './constants';
@ -149,7 +150,7 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
return '';
};
export const getMessageTranslation = (message: { translations: any }, autoTranslateLanguage: string) => {
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
if (!autoTranslateLanguage) {
return null;
}

View File

@ -7,4 +7,4 @@ export interface IFrequentlyUsedEmoji {
count: number;
}
export type TFrequentlyUsedEmoji = IFrequentlyUsedEmoji & Model;
export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model;

View File

@ -15,4 +15,4 @@ export interface ILoggedUser {
enableMessageParserEarlyAdoption?: boolean;
}
export type TLoggedUser = ILoggedUser & Model;
export type TLoggedUserModel = ILoggedUser & Model;

View File

@ -7,4 +7,4 @@ export interface IServerHistory {
updatedAt: Date;
}
export type TServerHistory = IServerHistory & Model;
export type TServerHistoryModel = IServerHistory & Model;

View File

@ -79,8 +79,6 @@ export interface ISubscription {
avatarETag?: string;
teamId?: string;
teamMain?: boolean;
search?: boolean;
username?: string;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: Relation<TMessageModel>;
threads: Relation<TThreadModel>;

View File

@ -43,10 +43,10 @@ export interface IThread {
id: string;
msg?: string;
t?: SubscriptionType;
rid?: string;
_updatedAt?: Date;
ts?: Date;
u?: IUserMessage;
rid: string;
_updatedAt: Date;
ts: Date;
u: IUserMessage;
alias?: string;
parseUrls?: boolean;
groupable?: boolean;
@ -64,8 +64,8 @@ export interface IThread {
dcount?: number;
dlm?: number;
tmid?: string;
tcount: number | string;
tlm?: string;
tcount?: number;
tlm?: Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];

View File

@ -3,12 +3,23 @@ import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
export * from './IAttachment';
export * from './IMessage';
export * from './INotification';
export * from './IRoom';
export * from './IServer';
export * from './ISubscription';
export * from './IPreferences';
export * from './ISubscription';
export * from './IRoom';
export * from './IMessage';
export * from './IThread';
export * from './IThreadMessage';
export * from './ICustomEmoji';
export * from './IFrequentlyUsedEmoji';
export * from './IUpload';
export * from './ISettings';
export * from './IRole';
export * from './IPermission';
export * from './ISlashCommand';
export * from './IUser';
export * from './IServer';
export * from './ILoggedUser';
export * from './IServerHistory';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {

View File

@ -16,7 +16,6 @@ import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IConnect } from '../../reducers/connect';
import { ISettings } from '../../reducers/settings';
export interface IApplicationState {
settings: ISettings;
login: any;
@ -49,4 +48,4 @@ export type TApplicationActions = TActionActiveUsers &
IActionSettings &
TActionEncryption &
TActionSortPreferences &
TActionUserTyping;
TActionUserTyping;

View File

@ -25,6 +25,7 @@ import serversSchema from './schema/servers';
import appSchema from './schema/app';
import migrations from './model/migrations';
import serversMigrations from './model/servers/migrations';
import { TAppDatabase, TServerDatabase } from './interfaces';
const appGroupPath = isIOS ? appGroup.path : '';
@ -32,9 +33,9 @@ if (__DEV__ && isIOS) {
console.log(appGroupPath);
}
const getDatabasePath = name => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
const getDatabasePath = (name: string) => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
export const getDatabase = (database = '') => {
export const getDatabase = (database = ''): Database => {
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
const dbName = getDatabasePath(path);
@ -64,8 +65,14 @@ export const getDatabase = (database = '') => {
});
};
interface IDatabases {
shareDB?: TAppDatabase;
serversDB: TServerDatabase;
activeDB?: TAppDatabase;
}
class DB {
databases = {
databases: IDatabases = {
serversDB: new Database({
adapter: new SQLiteAdapter({
dbName: getDatabasePath('default'),
@ -73,11 +80,12 @@ class DB {
migrations: serversMigrations
}),
modelClasses: [Server, LoggedUser, ServersHistory]
})
}) as TServerDatabase
};
get active() {
return this.databases.shareDB || this.databases.activeDB;
// Expected at least one database
get active(): TAppDatabase {
return this.databases.shareDB || this.databases.activeDB!;
}
get share() {
@ -116,11 +124,11 @@ class DB {
Setting,
User
]
});
}) as TAppDatabase;
}
setActiveDB(database) {
this.databases.activeDB = getDatabase(database);
setActiveDB(database: string) {
this.databases.activeDB = getDatabase(database) as TAppDatabase;
}
}

View File

@ -0,0 +1,72 @@
import { Database, Collection } from '@nozbe/watermelondb';
import * as models from './model';
import * as definitions from '../../definitions';
export type TAppDatabaseNames =
| typeof models.SUBSCRIPTIONS_TABLE
| typeof models.ROOMS_TABLE
| typeof models.MESSAGES_TABLE
| typeof models.THREADS_TABLE
| typeof models.THREAD_MESSAGES_TABLE
| typeof models.CUSTOM_EMOJIS_TABLE
| typeof models.FREQUENTLY_USED_EMOJIS_TABLE
| typeof models.UPLOADS_TABLE
| typeof models.SETTINGS_TABLE
| typeof models.ROLES_TABLE
| typeof models.PERMISSIONS_TABLE
| typeof models.SLASH_COMMANDS_TABLE
| typeof models.USERS_TABLE;
// Verify if T extends one type from TAppDatabaseNames, and if is truly,
// returns the specific model type.
// https://stackoverflow.com/a/54166010 TypeScript function return type based on input parameter
type ObjectType<T> = T extends typeof models.SUBSCRIPTIONS_TABLE
? definitions.TSubscriptionModel
: T extends typeof models.ROOMS_TABLE
? definitions.TRoomModel
: T extends typeof models.MESSAGES_TABLE
? definitions.TMessageModel
: T extends typeof models.THREADS_TABLE
? definitions.TThreadModel
: T extends typeof models.THREAD_MESSAGES_TABLE
? definitions.TThreadMessageModel
: T extends typeof models.CUSTOM_EMOJIS_TABLE
? definitions.TCustomEmojiModel
: T extends typeof models.FREQUENTLY_USED_EMOJIS_TABLE
? definitions.TFrequentlyUsedEmojiModel
: T extends typeof models.UPLOADS_TABLE
? definitions.TUploadModel
: T extends typeof models.SETTINGS_TABLE
? definitions.TSettingsModel
: T extends typeof models.ROLES_TABLE
? definitions.TRoleModel
: T extends typeof models.PERMISSIONS_TABLE
? definitions.TPermissionModel
: T extends typeof models.SLASH_COMMANDS_TABLE
? definitions.TSlashCommandModel
: T extends typeof models.USERS_TABLE
? definitions.TUserModel
: never;
export type TAppDatabase = {
get: <T extends TAppDatabaseNames>(db: T) => Collection<ObjectType<T>>;
} & Database;
// Migration to server database
export type TServerDatabaseNames =
| typeof models.SERVERS_TABLE
| typeof models.LOGGED_USERS_TABLE
| typeof models.SERVERS_HISTORY_TABLE;
type ObjectServerType<T> = T extends typeof models.SERVERS_TABLE
? definitions.TServerModel
: T extends typeof models.LOGGED_USERS_TABLE
? definitions.TLoggedUserModel
: T extends typeof models.SERVERS_HISTORY_TABLE
? definitions.TServerHistoryModel
: never;
export type TServerDatabase = {
get: <T extends TServerDatabaseNames>(db: T) => Collection<ObjectServerType<T>>;
} & Database;

View File

@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const CUSTOM_EMOJIS_TABLE = 'custom_emojis';
export default class CustomEmoji extends Model {
static table = 'custom_emojis';
static table = CUSTOM_EMOJIS_TABLE;
@field('name') name;

View File

@ -1,8 +1,9 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
export const FREQUENTLY_USED_EMOJIS_TABLE = 'frequently_used_emojis';
export default class FrequentlyUsedEmoji extends Model {
static table = 'frequently_used_emojis';
static table = FREQUENTLY_USED_EMOJIS_TABLE;
@field('content') content;

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const TABLE_NAME = 'messages';
export const MESSAGES_TABLE = 'messages';
export default class Message extends Model {
static table = TABLE_NAME;
static table = MESSAGES_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,8 +3,10 @@ import { date, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const PERMISSIONS_TABLE = 'permissions';
export default class Permission extends Model {
static table = 'permissions';
static table = PERMISSIONS_TABLE;
@json('roles', sanitizer) roles;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
export const ROLES_TABLE = 'roles';
export default class Role extends Model {
static table = 'roles';
static table = ROLES_TABLE;
@field('description') description;
}

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const ROOMS_TABLE = 'rooms';
export default class Room extends Model {
static table = 'rooms';
static table = ROOMS_TABLE;
@json('custom_fields', sanitizer) customFields;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { date, field, readonly } from '@nozbe/watermelondb/decorators';
export const SERVERS_HISTORY_TABLE = 'servers_history';
export default class ServersHistory extends Model {
static table = 'servers_history';
static table = SERVERS_HISTORY_TABLE;
@field('url') url;

View File

@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const SETTINGS_TABLE = 'settings';
export default class Setting extends Model {
static table = 'settings';
static table = SETTINGS_TABLE;
@field('value_as_string') valueAsString;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
export const SLASH_COMMANDS_TABLE = 'slash_commands';
export default class SlashCommand extends Model {
static table = 'slash_commands';
static table = SLASH_COMMANDS_TABLE;
@field('params') params;

View File

@ -3,10 +3,10 @@ import { children, date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const TABLE_NAME = 'subscriptions';
export const SUBSCRIPTIONS_TABLE = 'subscriptions';
export default class Subscription extends Model {
static table = TABLE_NAME;
static table = SUBSCRIPTIONS_TABLE;
static associations = {
messages: { type: 'has_many', foreignKey: 'rid' },

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const TABLE_NAME = 'threads';
export const THREADS_TABLE = 'threads';
export default class Thread extends Model {
static table = TABLE_NAME;
static table = THREADS_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const TABLE_NAME = 'thread_messages';
export const THREAD_MESSAGES_TABLE = 'thread_messages';
export default class ThreadMessage extends Model {
static table = TABLE_NAME;
static table = THREAD_MESSAGES_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'subscription_id' }

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field, relation } from '@nozbe/watermelondb/decorators';
export const UPLOADS_TABLE = 'uploads';
export default class Upload extends Model {
static table = 'uploads';
static table = UPLOADS_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
export const USERS_TABLE = 'users';
export default class User extends Model {
static table = 'users';
static table = USERS_TABLE;
@field('_id') _id;

View File

@ -0,0 +1,16 @@
export * from './CustomEmoji';
export * from './FrequentlyUsedEmoji';
export * from './Message';
export * from './Permission';
export * from './Role';
export * from './Room';
export * from './Setting';
export * from './SlashCommand';
export * from './Subscription';
export * from './Thread';
export * from './ThreadMessage';
export * from './Upload';
export * from './User';
export * from './ServersHistory';
export * from './servers/Server';
export * from './servers/User';

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { date, field } from '@nozbe/watermelondb/decorators';
export const SERVERS_TABLE = 'servers';
export default class Server extends Model {
static table = 'servers';
static table = SERVERS_TABLE;
@field('name') name;

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../../utils';
export const LOGGED_USERS_TABLE = 'users';
export default class User extends Model {
static table = 'users';
static table = LOGGED_USERS_TABLE;
@field('token') token;

View File

@ -1,7 +1,7 @@
import database from '..';
import { TABLE_NAME } from '../model/Message';
import { MESSAGES_TABLE } from '../model/Message';
const getCollection = db => db.get(TABLE_NAME);
const getCollection = db => db.get(MESSAGES_TABLE);
export const getMessageById = async messageId => {
const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..';
import { TABLE_NAME } from '../model/Subscription';
import { SUBSCRIPTIONS_TABLE } from '../model/Subscription';
const getCollection = db => db.get(TABLE_NAME);
const getCollection = db => db.get(SUBSCRIPTIONS_TABLE);
export const getSubscriptionByRoomId = async rid => {
const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..';
import { TABLE_NAME } from '../model/Thread';
import { THREADS_TABLE } from '../model/Thread';
const getCollection = db => db.get(TABLE_NAME);
const getCollection = db => db.get(THREADS_TABLE);
export const getThreadById = async tmid => {
const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..';
import { TABLE_NAME } from '../model/ThreadMessage';
import { THREAD_MESSAGES_TABLE } from '../model/ThreadMessage';
const getCollection = db => db.get(TABLE_NAME);
const getCollection = db => db.get(THREAD_MESSAGES_TABLE);
export const getThreadMessageById = async messageId => {
const db = database.active;

View File

@ -61,7 +61,7 @@ const PERMISSIONS = [
export async function setPermissions() {
const db = database.active;
const permissionsCollection = db.collections.get('permissions');
const permissionsCollection = db.get('permissions');
const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});

View File

@ -8,7 +8,7 @@ import protectedFunction from './helpers/protectedFunction';
export async function setRoles() {
const db = database.active;
const rolesCollection = db.collections.get('roles');
const rolesCollection = db.get('roles');
const allRoles = await rolesCollection.query().fetch();
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
reduxStore.dispatch(setRolesAction(parsed));

View File

@ -31,9 +31,8 @@ const navigate = ({
};
interface IItem extends Partial<ISubscription> {
rid: string;
name: string;
t: SubscriptionType;
search?: boolean; // comes from spotlight
username?: string;
}
export const goRoom = async ({
@ -46,7 +45,7 @@ export const goRoom = async ({
navigationMethod?: any;
jumpToMessageId?: string;
}): Promise<void> => {
if (item.t === 'd' && item.search) {
if (item.t === SubscriptionType.DIRECT && item?.search) {
// if user is using the search we need first to join/create room
try {
const { username } = item;
@ -55,7 +54,7 @@ export const goRoom = async ({
return navigate({
item: {
rid: result.room._id,
name: username!,
name: username,
t: SubscriptionType.DIRECT
},
isMasterDetail,

View File

@ -22,11 +22,11 @@ import { goRoom } from '../utils/goRoom';
import { showErrorAlert } from '../utils/info';
import debounce from '../utils/debounce';
import { ChatsStackParamList } from '../stacks/types';
import { IRoom } from '../definitions/IRoom';
import { TSubscriptionModel, SubscriptionType } from '../definitions';
interface IAddExistingChannelViewState {
search: Array<IRoom>;
channels: Array<IRoom>;
search: TSubscriptionModel[];
channels: TSubscriptionModel[];
selected: string[];
loading: boolean;
}
@ -83,7 +83,7 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
try {
const { addTeamChannelPermission } = this.props;
const db = database.active;
const channels = await db.collections
const channels = await db
.get('subscriptions')
.query(
Q.where('team_id', ''),
@ -94,9 +94,9 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
)
.fetch();
const asyncFilter = async (channelsArray: Array<IRoom>) => {
const asyncFilter = async (channelsArray: TSubscriptionModel[]) => {
const results = await Promise.all(
channelsArray.map(async (channel: IRoom) => {
channelsArray.map(async channel => {
if (channel.prid) {
return false;
}
@ -173,11 +173,10 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
}
};
// TODO: refactor with Room Model
renderItem = ({ item }: { item: any }) => {
renderItem = ({ item }: { item: TSubscriptionModel }) => {
const isChecked = this.isChecked(item.rid);
// TODO: reuse logic inside RoomTypeIcon
const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
const icon = item.t === SubscriptionType.DIRECT && !item?.teamId ? 'channel-private' : 'channel-public';
return (
<List.Item
title={RocketChat.getRoomTitle(item)}

View File

@ -7,6 +7,7 @@ import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n';
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { themes } from '../../constants/colors';
import { TSubscriptionModel } from '../../definitions/ISubscription';
import styles from './styles';
import { ICreateDiscussionViewSelectChannel } from './interfaces';
@ -20,7 +21,7 @@ const SelectChannel = ({
serverVersion,
theme
}: ICreateDiscussionViewSelectChannel): JSX.Element => {
const [channels, setChannels] = useState([]);
const [channels, setChannels] = useState<TSubscriptionModel[]>([]);
const getChannels = debounce(async (keyword = '') => {
try {

View File

@ -38,11 +38,11 @@ const SelectUsers = ({
const res = await RocketChat.search({ text: keyword, filterRooms: false });
let items = [
...users.filter((u: IUser) => selected.includes(u.name)),
...res.filter((r: IUser) => !users.find((u: IUser) => u.name === r.name))
...res.filter(r => !users.find((u: IUser) => u.name === r.name))
];
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();
items = items.map(item => {
const index = records.findIndex((r: IUser) => r.username === item.name);
const index = records.findIndex(r => r.username === item.name);
if (index > -1) {
const record = records[index];
return {

View File

@ -24,6 +24,7 @@ import { createChannelRequest } from '../actions/createChannel';
import { goRoom } from '../utils/goRoom';
import SafeAreaView from '../containers/SafeAreaView';
import { compareServerVersion, methods } from '../lib/utils';
import { TSubscriptionModel } from '../definitions';
import sharedStyles from './Styles';
const QUERY_SIZE = 50;
@ -68,9 +69,8 @@ interface ISearch {
}
interface INewMessageViewState {
search: ISearch[];
// TODO: Refactor when migrate room
chats: any[];
search: (ISearch | TSubscriptionModel)[];
chats: TSubscriptionModel[];
permissions: boolean[];
}
@ -108,7 +108,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
init = async () => {
try {
const db = database.active;
const chats = await db.collections
const chats = await db
.get('subscriptions')
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
.fetch();
@ -153,7 +153,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
};
search = async (text: string) => {
const result = await RocketChat.search({ text, filterRooms: false });
const result: ISearch[] | TSubscriptionModel[] = await RocketChat.search({ text, filterRooms: false });
this.setState({
search: result
});
@ -280,8 +280,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
);
};
// TODO: Refactor when migrate room
renderItem = ({ item, index }: { item: ISearch | any; index: number }) => {
renderItem = ({ item, index }: { item: ISearch | TSubscriptionModel; index: number }) => {
const { search, chats } = this.state;
const { theme } = this.props;
@ -295,10 +294,14 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
if (search.length === 0 && index === chats.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={item.search ? item.name : item.fname}
username={item.search ? item.username : item.name}
name={itemSearch.search ? itemSearch.name : itemModel.fname || ''}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => this.goRoom(item)}
testID={`new-message-view-item-${item.name}`}
style={style}

View File

@ -6,7 +6,7 @@ import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import sharedStyles from '../../Styles';
import Touch from '../../../utils/touch';
import { TServerHistory } from '../../../definitions/IServerHistory';
import { TServerHistoryModel } from '../../../definitions/IServerHistory';
const styles = StyleSheet.create({
container: {
@ -28,10 +28,10 @@ const styles = StyleSheet.create({
});
interface IItem {
item: TServerHistory;
item: TServerHistoryModel;
theme: string;
onPress(url: string): void;
onDelete(item: TServerHistory): void;
onDelete(item: TServerHistoryModel): void;
}
const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => (

View File

@ -5,7 +5,7 @@ import TextInput from '../../../containers/TextInput';
import * as List from '../../../containers/List';
import { themes } from '../../../constants/colors';
import I18n from '../../../i18n';
import { TServerHistory } from '../../../definitions/IServerHistory';
import { TServerHistoryModel } from '../../../definitions/IServerHistory';
import Item from './Item';
const styles = StyleSheet.create({
@ -33,8 +33,8 @@ interface IServerInput extends TextInputProps {
theme: string;
serversHistory: any[];
onSubmit(): void;
onDelete(item: TServerHistory): void;
onPressServerHistory(serverHistory: TServerHistory): void;
onDelete(item: TServerHistoryModel): void;
onPressServerHistory(serverHistory: TServerHistoryModel): void;
}
const ServerInput = ({

View File

@ -14,7 +14,7 @@ import Button from '../../containers/Button';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
import * as HeaderButton from '../../containers/HeaderButton';
import OrSeparator from '../../containers/OrSeparator';
import { IBaseScreen, TServerHistory } from '../../definitions';
import { IBaseScreen, TServerHistoryModel } from '../../definitions';
import { withDimensions } from '../../dimensions';
import I18n from '../../i18n';
import database from '../../lib/database';
@ -77,7 +77,7 @@ interface INewServerViewState {
text: string;
connectingOpen: boolean;
certificate: any;
serversHistory: TServerHistory[];
serversHistory: TServerHistoryModel[];
}
interface ISubmitParams {
@ -153,7 +153,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
const likeString = sanitizeLikeString(text);
whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))];
}
const serversHistory = (await serversHistoryCollection.query(...whereClause).fetch()) as TServerHistory[];
const serversHistory = await serversHistoryCollection.query(...whereClause).fetch();
this.setState({ serversHistory });
} catch {
// Do nothing
@ -177,7 +177,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
dispatch(serverRequest(server));
};
onPressServerHistory = (serverHistory: TServerHistory) => {
onPressServerHistory = (serverHistory: TServerHistoryModel) => {
this.setState({ text: serverHistory.url }, () => this.submit({ fromServerHistory: true, username: serverHistory?.username }));
};
@ -269,14 +269,14 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
});
};
deleteServerHistory = async (item: TServerHistory) => {
deleteServerHistory = async (item: TServerHistoryModel) => {
const db = database.servers;
try {
await db.write(async () => {
await item.destroyPermanently();
});
this.setState((prevstate: INewServerViewState) => ({
serversHistory: prevstate.serversHistory.filter((server: TServerHistory) => server.id !== item.id)
serversHistory: prevstate.serversHistory.filter(server => server.id !== item.id)
}));
} catch {
// Nothing

View File

@ -80,7 +80,7 @@ class NotificationPreferencesView extends React.Component<INotificationPreferenc
const db = database.active;
try {
await db.action(async () => {
await db.write(async () => {
await room.update(
protectedFunction((r: any) => {
r[key] = value;
@ -97,7 +97,7 @@ class NotificationPreferencesView extends React.Component<INotificationPreferenc
// do nothing
}
await db.action(async () => {
await db.write(async () => {
await room.update(
protectedFunction((r: any) => {
r[key] = room[key];

View File

@ -643,7 +643,7 @@ class RoomActionsView extends React.Component {
const { addTeamChannelPermission, createTeamPermission } = this.props;
const QUERY_SIZE = 50;
const db = database.active;
const teams = await db.collections
const teams = await db
.get('subscriptions')
.query(
Q.where('team_main', true),

View File

@ -144,11 +144,11 @@ class ListContainer extends React.Component {
if (tmid) {
try {
this.thread = await db.collections.get('threads').find(tmid);
this.thread = await db.get('threads').find(tmid);
} catch (e) {
console.log(e);
}
this.messagesObservable = db.collections
this.messagesObservable = db
.get('thread_messages')
.query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(this.count))
.observe();
@ -162,7 +162,7 @@ class ListContainer extends React.Component {
if (!showMessageInMainThread) {
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
}
this.messagesObservable = db.collections
this.messagesObservable = db
.get('messages')
.query(...whereClause)
.observe();

View File

@ -91,7 +91,7 @@ class UploadProgress extends Component {
}
const db = database.active;
this.uploadsObservable = db.collections.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']);
this.uploadsObservable = db.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']);
this.uploadsSubscription = this.uploadsObservable.subscribe(uploads => {
if (this.mounted) {

View File

@ -690,7 +690,7 @@ class RoomView extends React.Component {
// eslint-disable-next-line react/sort-comp
updateUnreadCount = async () => {
const db = database.active;
const observable = await db.collections
const observable = await db
.get('subscriptions')
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid)))
.observeWithColumns(['unread']);

View File

@ -47,7 +47,7 @@ class ServerDropdown extends Component {
async componentDidMount() {
const serversDB = database.servers;
const observable = await serversDB.collections.get('servers').query().observeWithColumns(['name']);
const observable = await serversDB.get('servers').query().observeWithColumns(['name']);
this.subscription = observable.subscribe(data => {
this.setState({ servers: data });

View File

@ -450,7 +450,7 @@ class RoomsListView extends React.Component {
// When we're grouping by something
if (this.isGrouping) {
observable = await db.collections
observable = await db
.get('subscriptions')
.query(...defaultWhereClause)
.observeWithColumns(['alert']);
@ -458,7 +458,7 @@ class RoomsListView extends React.Component {
// When we're NOT grouping
} else {
this.count += QUERY_SIZE;
observable = await db.collections
observable = await db
.get('subscriptions')
.query(...defaultWhereClause, Q.experimentalSkip(0), Q.experimentalTake(this.count))
.observe();

View File

@ -92,7 +92,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
try {
this.serverRecord = (await serversCollection.find(server)) as TServerModel;
this.serverRecord = await serversCollection.find(server);
this.setState({
autoLock: this.serverRecord?.autoLock,
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
@ -115,7 +115,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
*/
observe = () => {
this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => {
this.setState({ biometry });
this.setState({ biometry: !!biometry });
});
};

View File

@ -109,10 +109,7 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
init = async () => {
try {
const db = database.active;
const observable = await db.collections
.get('subscriptions')
.query(Q.where('t', 'd'))
.observeWithColumns(['room_updated_at']);
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
// TODO: Refactor when migrate room
this.querySubscription = observable.subscribe((data: any) => {
@ -129,7 +126,9 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
}
search = async (text: string) => {
const result = await RocketChat.search({ text, filterRooms: false });
// TODO: When migrate rocketchat.js pass the param IUser to there and the return should be
// IUser | TSubscriptionModel, this because we do a local search too
const result = (await RocketChat.search({ text, filterRooms: false })) as ISelectedUser[];
this.setState({
search: result
});

View File

@ -7,7 +7,6 @@ import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { Observable, Subscription } from 'rxjs';
import Model from '@nozbe/watermelondb/Model';
import Database from '@nozbe/watermelondb/Database';
import ActivityIndicator from '../../containers/ActivityIndicator';
@ -86,7 +85,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
private messagesSubscription?: Subscription;
private messagesObservable!: Observable<Model>;
private messagesObservable?: Observable<TThreadModel[]>;
constructor(props: IThreadMessagesViewProps) {
super(props);
@ -188,7 +187,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
const db = database.active;
// subscription query
const subscription = (await db.collections.get('subscriptions').find(this.rid)) as TSubscriptionModel;
const subscription = await db.get('subscriptions').find(this.rid);
const observable = subscription.observe();
this.subSubscription = observable.subscribe(data => {
this.setState({ subscription: data });
@ -214,15 +213,15 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
whereClause.push(Q.where('msg', Q.like(`%${sanitizeLikeString(searchText.trim())}%`)));
}
this.messagesObservable = db.collections
this.messagesObservable = db
.get('threads')
.query(...whereClause)
.observeWithColumns(['updated_at']);
// TODO: Refactor when migrate messages
this.messagesSubscription = this.messagesObservable.subscribe((messages: any) => {
this.messagesSubscription = this.messagesObservable.subscribe(messages => {
const { currentFilter } = this.state;
const displayingThreads = this.getFilteredThreads(messages, subscription!, currentFilter);
const displayingThreads = this.getFilteredThreads(messages, subscription, currentFilter);
if (this.mounted) {
this.setState({ messages, displayingThreads });
} else {
@ -419,14 +418,14 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
};
// helper to query threads
getFilteredThreads = (messages: any, subscription: TSubscriptionModel, currentFilter?: Filter): TThreadModel[] => {
getFilteredThreads = (messages: TThreadModel[], subscription?: TSubscriptionModel, currentFilter?: Filter): TThreadModel[] => {
// const { currentFilter } = this.state;
const { user } = this.props;
if (currentFilter === Filter.Following) {
return messages?.filter((item: { replies: any[] }) => item?.replies?.find(u => u === user.id));
return messages?.filter(item => item?.replies?.find(u => u === user.id));
}
if (currentFilter === Filter.Unread) {
return messages?.filter((item: { id: string }) => subscription?.tunread?.includes(item?.id));
return messages?.filter(item => subscription?.tunread?.includes(item?.id));
}
return messages;
};