diff --git a/app/actions/permissions.ts b/app/actions/permissions.ts index d47118fce..0c79b2e85 100644 --- a/app/actions/permissions.ts +++ b/app/actions/permissions.ts @@ -1,26 +1,26 @@ import { Action } from 'redux'; -import { IPermissions } from '../reducers/permissions'; +import { IPermissionsState, TSupportedPermissions } from '../reducers/permissions'; import { PERMISSIONS } from './actionsTypes'; interface ISetPermissions extends Action { - permissions: IPermissions; + permissions: IPermissionsState; } interface IUpdatePermissions extends Action { - payload: { id: string; roles: string }; + payload: { id: TSupportedPermissions; roles: string[] }; } export type TActionPermissions = ISetPermissions & IUpdatePermissions; -export function setPermissions(permissions: IPermissions): ISetPermissions { +export function setPermissions(permissions: IPermissionsState): ISetPermissions { return { type: PERMISSIONS.SET, permissions }; } -export function updatePermission(id: string, roles: string): IUpdatePermissions { +export function updatePermission(id: TSupportedPermissions, roles: string[]): IUpdatePermissions { return { type: PERMISSIONS.UPDATE, payload: { id, roles } diff --git a/app/definitions/IPermission.ts b/app/definitions/IPermission.ts index 0ccc13465..5583f3edd 100644 --- a/app/definitions/IPermission.ts +++ b/app/definitions/IPermission.ts @@ -1,9 +1,9 @@ import Model from '@nozbe/watermelondb/Model'; export interface IPermission { - id: string; + _id: string; roles: string[]; - _updatedAt: Date; + _updatedAt: Date | string; } export type TPermissionModel = IPermission & Model; diff --git a/app/definitions/redux/index.ts b/app/definitions/redux/index.ts index dc5033771..e23f5e321 100644 --- a/app/definitions/redux/index.ts +++ b/app/definitions/redux/index.ts @@ -13,6 +13,8 @@ import { IActionSettings } from '../../actions/settings'; import { TActionsShare } from '../../actions/share'; import { TActionSortPreferences } from '../../actions/sortPreferences'; import { TActionUserTyping } from '../../actions/usersTyping'; +import { TActionPermissions } from '../../actions/permissions'; +import { TActionEnterpriseModules } from '../../actions/enterpriseModules'; // REDUCERS import { IActiveUsers } from '../../reducers/activeUsers'; import { IApp } from '../../reducers/app'; @@ -26,8 +28,8 @@ import { ISelectedUsers } from '../../reducers/selectedUsers'; import { IServer } from '../../reducers/server'; import { ISettings } from '../../reducers/settings'; import { IShare } from '../../reducers/share'; +import { IPermissionsState } from '../../reducers/permissions'; import { IEnterpriseModules } from '../../reducers/enterpriseModules'; -import { TActionEnterpriseModules } from '../../actions/enterpriseModules'; export interface IApplicationState { settings: ISettings; @@ -49,7 +51,7 @@ export interface IApplicationState { inquiry: any; enterpriseModules: IEnterpriseModules; encryption: IEncryption; - permissions: any; + permissions: IPermissionsState; roles: IRoles; } @@ -67,4 +69,5 @@ export type TApplicationActions = TActionActiveUsers & TActionsShare & TActionServer & TActionApp & + TActionPermissions & TActionEnterpriseModules; diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.ts similarity index 58% rename from app/lib/methods/getPermissions.js rename to app/lib/methods/getPermissions.ts index cba8a7652..fa807d7fa 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.ts @@ -7,10 +7,12 @@ import database from '../database'; import log from '../../utils/log'; import { store as reduxStore } from '../auxStore'; import RocketChat from '../rocketchat'; +import sdk from '../rocketchat/services/sdk'; import { setPermissions as setPermissionsAction } from '../../actions/permissions'; import protectedFunction from './helpers/protectedFunction'; +import { TPermissionModel, IPermission } from '../../definitions'; -const PERMISSIONS = [ +export const SUPPORTED_PERMISSIONS = [ 'add-user-to-any-c-room', 'add-user-to-any-p-room', 'add-user-to-joined-room', @@ -57,18 +59,20 @@ const PERMISSIONS = [ 'edit-livechat-room-customfields', 'view-canned-responses', 'mobile-upload-file' -]; +] as const; -export async function setPermissions() { +export async function setPermissions(): Promise { const db = database.active; const permissionsCollection = db.get('permissions'); - const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch(); + const allPermissions = await permissionsCollection + .query(Q.where('id', Q.oneOf(SUPPORTED_PERMISSIONS as unknown as string[]))) + .fetch(); const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {}); reduxStore.dispatch(setPermissionsAction(parsed)); } -const getUpdatedSince = allRecords => { +const getUpdatedSince = (allRecords: TPermissionModel[]) => { try { if (!allRecords.length) { return null; @@ -78,57 +82,63 @@ const getUpdatedSince = allRecords => { ['_updatedAt'], ['desc'] ); - return ordered && ordered[0]._updatedAt.toISOString(); + return new Date(ordered[0]._updatedAt).toISOString(); } catch (e) { log(e); } return null; }; -const updatePermissions = async ({ update = [], remove = [], allRecords }) => { +const updatePermissions = async ({ + update = [], + remove = [], + allRecords +}: { + update?: IPermission[]; + remove?: IPermission[]; + allRecords: TPermissionModel[]; +}) => { if (!((update && update.length) || (remove && remove.length))) { return; } const db = database.active; const permissionsCollection = db.get('permissions'); - // filter permissions - let permissionsToCreate = []; - let permissionsToUpdate = []; - let permissionsToDelete = []; + const batch: TPermissionModel[] = []; + + // Delete + if (remove?.length) { + const filteredPermissionsToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id)); + const permissionsToDelete = filteredPermissionsToDelete.map(permission => permission.prepareDestroyPermanently()); + batch.push(...permissionsToDelete); + } // Create or update - if (update && update.length) { - permissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id)); - permissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); - permissionsToCreate = permissionsToCreate.map(permission => + if (update?.length) { + const filteredPermissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id)); + const filteredPermissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); + const permissionsToCreate = filteredPermissionsToCreate.map(permission => permissionsCollection.prepareCreate( - protectedFunction(p => { + protectedFunction((p: TPermissionModel) => { p._raw = sanitizedRaw({ id: permission._id }, permissionsCollection.schema); Object.assign(p, permission); }) ) ); - permissionsToUpdate = permissionsToUpdate.map(permission => { + const permissionsToUpdate = filteredPermissionsToUpdate.map(permission => { const newPermission = update.find(p => p._id === permission.id); return permission.prepareUpdate( - protectedFunction(p => { + protectedFunction((p: TPermissionModel) => { Object.assign(p, newPermission); }) ); }); - } - // Delete - if (remove && remove.length) { - permissionsToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id)); - permissionsToDelete = permissionsToDelete.map(permission => permission.prepareDestroyPermanently()); + batch.push(...permissionsToCreate, ...permissionsToUpdate); } - const batch = [...permissionsToCreate, ...permissionsToUpdate, ...permissionsToDelete]; - try { - await db.action(async () => { + await db.write(async () => { await db.batch(...batch); }); return true; @@ -137,18 +147,19 @@ const updatePermissions = async ({ update = [], remove = [], allRecords }) => { } }; -export function getPermissions() { +export function getPermissions(): Promise { return new Promise(async resolve => { try { - const serverVersion = reduxStore.getState().server.version; + const serverVersion: string | null = reduxStore.getState().server.version; const db = database.active; const permissionsCollection = db.get('permissions'); const allRecords = await permissionsCollection.query().fetch(); RocketChat.subscribe('stream-notify-logged', 'permissions-changed'); // if server version is lower than 0.73.0, fetches from old api - if (compareServerVersion(serverVersion, 'lowerThan', '0.73.0')) { + if (serverVersion && compareServerVersion(serverVersion, 'lowerThan', '0.73.0')) { // RC 0.66.0 - const result = await this.sdk.get('permissions.list'); + // @ts-ignore + const result: any = await sdk.get('permissions.list'); if (!result.success) { return resolve(); } @@ -157,25 +168,25 @@ export function getPermissions() { setPermissions(); } return resolve(); - } else { - const params = {}; - const updatedSince = getUpdatedSince(allRecords); - if (updatedSince) { - params.updatedSince = updatedSince; - } - // RC 0.73.0 - const result = await this.sdk.get('permissions.listAll', params); + } - if (!result.success) { - return resolve(); - } + const params: { updatedSince?: string } = {}; + const updatedSince = getUpdatedSince(allRecords); + if (updatedSince) { + params.updatedSince = updatedSince; + } + // RC 0.73.0 + const result = await sdk.get('permissions.listAll', params); - const changePermissions = await updatePermissions({ update: result.update, remove: result.delete, allRecords }); - if (changePermissions) { - setPermissions(); - } + if (!result.success) { return resolve(); } + + const changePermissions = await updatePermissions({ update: result.update, remove: result.remove, allRecords }); + if (changePermissions) { + setPermissions(); + } + return resolve(); } catch (e) { log(e); return resolve(); diff --git a/app/reducers/permissions.test.ts b/app/reducers/permissions.test.ts index 8980a4428..0d6d8b287 100644 --- a/app/reducers/permissions.test.ts +++ b/app/reducers/permissions.test.ts @@ -1,6 +1,6 @@ import { setPermissions, updatePermission } from '../actions/permissions'; import { mockedStore } from './mockedStore'; -import { initialState } from './permissions'; +import { initialState, IPermissionsState } from './permissions'; describe('test permissions reducer', () => { it('should return initial state', () => { @@ -9,15 +9,15 @@ describe('test permissions reducer', () => { }); it('should return modified store after setPermissions', () => { - const permissions = { hasEditPermission: 'enabled', hasForceDeletePermission: 'enabled' }; + const permissions: IPermissionsState = { 'add-user-to-any-c-room': ['admin'], 'add-team-channel': ['user'] }; mockedStore.dispatch(setPermissions(permissions)); const state = mockedStore.getState().permissions; expect(state).toEqual(permissions); }); it('should return empty store after remove user', () => { - mockedStore.dispatch(updatePermission('hasEditPermission', 'disabled')); + mockedStore.dispatch(updatePermission('add-team-channel', ['owner'])); const state = mockedStore.getState().permissions; - expect(state.hasEditPermission).toEqual('disabled'); + expect(state['add-team-channel']).toEqual(['owner']); }); }); diff --git a/app/reducers/permissions.ts b/app/reducers/permissions.ts index a80a4e87e..d2887a555 100644 --- a/app/reducers/permissions.ts +++ b/app/reducers/permissions.ts @@ -1,11 +1,16 @@ import { PERMISSIONS } from '../actions/actionsTypes'; import { TActionPermissions } from '../actions/permissions'; +import { SUPPORTED_PERMISSIONS } from '../lib/methods/getPermissions'; -export type IPermissions = Record; +export type TSupportedPermissions = typeof SUPPORTED_PERMISSIONS[number]; -export const initialState: IPermissions = {}; +export type IPermissionsState = { + [K in TSupportedPermissions]?: string[]; +}; -export default function permissions(state = initialState, action: TActionPermissions): IPermissions { +export const initialState: IPermissionsState = {}; + +export default function permissions(state = initialState, action: TActionPermissions): IPermissionsState { switch (action.type) { case PERMISSIONS.SET: return action.permissions; diff --git a/app/views/CreateChannelView.tsx b/app/views/CreateChannelView.tsx index 6a11639ba..43df75fbf 100644 --- a/app/views/CreateChannelView.tsx +++ b/app/views/CreateChannelView.tsx @@ -96,8 +96,8 @@ interface ICreateChannelViewProps extends IBaseScreen; route: RouteProp; theme: string; - editOmnichannelContact: string[]; - editLivechatRoomCustomfields: string[]; + editOmnichannelContact: string[] | undefined; + editLivechatRoomCustomfields: string[] | undefined; } const Title = ({ title, theme }: ITitle) => diff --git a/app/views/NewMessageView.tsx b/app/views/NewMessageView.tsx index 729f42b0d..1cc84a21e 100644 --- a/app/views/NewMessageView.tsx +++ b/app/views/NewMessageView.tsx @@ -77,11 +77,11 @@ interface INewMessageViewProps extends IBaseScreen { maxUsers: number; isMasterDetail: boolean; serverVersion: string; - createTeamPermission: string[]; - createDirectMessagePermission: string[]; - createPublicChannelPermission: string[]; - createPrivateChannelPermission: string[]; - createDiscussionPermission: string[]; + createTeamPermission: string[] | undefined; + createDirectMessagePermission: string[] | undefined; + createPublicChannelPermission: string[] | undefined; + createPrivateChannelPermission: string[] | undefined; + createDiscussionPermission: string[] | undefined; } class NewMessageView extends React.Component {