Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression: fix apps path #25809

Merged
merged 3 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions apps/meteor/app/api/server/v1/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
isMethodCallAnonProps,
isMeteorCall,
} from '@rocket.chat/rest-typings';
import { IUser } from '@rocket.chat/core-typings';

import { hasPermission } from '../../../authorization/server';
import { Users } from '../../../models/server';
Expand Down Expand Up @@ -166,17 +167,24 @@ API.v1.addRoute(
'me',
{ authRequired: true },
{
get() {
async get() {
const fields = getDefaultUserFields();
const user = Users.findOneById(this.userId, { fields });

// The password hash shouldn't be leaked but the client may need to know if it exists.
if (user?.services?.password?.bcrypt) {
user.services.password.exists = true;
delete user.services.password.bcrypt;
}

return API.v1.success(this.getUserInfo(user));
const { services, ...user } = Users.findOneById(this.userId, { fields }) as IUser;

return API.v1.success(
this.getUserInfo({
...user,
...(services && {
services: {
...services,
password: {
// The password hash shouldn't be leaked but the client may need to know if it exists.
exists: Boolean(services?.password?.bcrypt),
} as any,
},
}),
}),
);
},
},
);
Expand Down
19 changes: 4 additions & 15 deletions apps/meteor/app/apps/client/@types/IOrchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata/IAppInfo';
import { ISetting } from '@rocket.chat/apps-engine/definition/settings/ISetting';

export interface IDetailedDescription {
Expand Down Expand Up @@ -148,7 +147,6 @@ export interface IAppLanguage {

export interface IAppExternalURL {
url: string;
success: boolean;
}

export interface ICategory {
Expand All @@ -159,10 +157,10 @@ export interface ICategory {
title: string;
}

export interface IDeletedInstalledApp {
app: IAppInfo;
success: boolean;
}
// export interface IDeletedInstalledApp {
// app: IAppInfo;
// success: boolean;
// }

export interface IAppSynced {
app: IAppFromMarketplace;
Expand Down Expand Up @@ -193,12 +191,3 @@ export interface ISettingsReturn {
settings: ISettings;
success: boolean;
}

export interface ISettingsPayload {
settings: ISetting[];
}

export interface ISettingsSetReturn {
updated: ISettings;
success: boolean;
}
157 changes: 82 additions & 75 deletions apps/meteor/app/apps/client/orchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import type { ISetting } from '@rocket.chat/apps-engine/definition/settings';
import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager';
import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api';
import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPermission';
import { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage/IAppStorageItem';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { AppScreenshot, Serialized } from '@rocket.chat/core-typings';

import { App } from '../../../client/views/admin/apps/types';
import { dispatchToastMessage } from '../../../client/lib/toast';
Expand All @@ -15,27 +17,23 @@ import { createDeferredValue } from '../lib/misc/DeferredValue';
import {
IPricingPlan,
EAppPurchaseType,
IAppFromMarketplace,
// IAppFromMarketplace,
IAppLanguage,
IAppExternalURL,
ICategory,
IDeletedInstalledApp,
IAppSynced,
IAppScreenshots,
// IAppSynced,
// IAppScreenshots,
// IScreenshot,
IAuthor,
IDetailedChangelog,
IDetailedDescription,
ISubscriptionInfo,
ISettingsReturn,
ISettingsPayload,
ISettingsSetReturn,
} from './@types/IOrchestrator';
import { AppWebsocketReceiver } from './communication';
import { handleI18nResources } from './i18n';
import { RealAppsEngineUIHost } from './RealAppsEngineUIHost';

const { APIClient } = require('../../utils');
const { hasAtLeastOnePermission } = require('../../authorization');
import { APIClient } from '../../utils/client';
import { hasAtLeastOnePermission } from '../../authorization/client';

export interface IAppsFromMarketplace {
price: number;
Expand Down Expand Up @@ -123,88 +121,97 @@ class AppClientOrchestrator {
}
}

public screenshots(appId: string): IAppScreenshots {
return APIClient.get(`/v1/apps/${appId}/screenshots`);
public async screenshots(appId: string): Promise<AppScreenshot[]> {
const { screenshots } = await APIClient.get(`/apps/${appId}/screenshots`);
return screenshots;
}

public isEnabled(): Promise<boolean> | undefined {
return this.deferredIsEnabled;
}

public async getApps(): Promise<App[]> {
const { apps } = await APIClient.get('/v1/apps');
return apps;
const result = await APIClient.get('/apps');
if ('apps' in result) {
return result.apps;
}
throw new Error('Apps not found');
}

public async getAppsFromMarketplace(): Promise<IAppsFromMarketplace[]> {
const appsOverviews: IAppFromMarketplace[] = await APIClient.get('/v1/apps', { marketplace: 'true' });
return appsOverviews.map((app: IAppFromMarketplace) => {
const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app;
return {
...latest,
price,
pricingPlans,
purchaseType,
isEnterpriseOnly,
modifiedAt,
};
});
public async getAppsFromMarketplace(): Promise<App[]> {
const result = await APIClient.get('/apps', { marketplace: 'true' });

if ('apps' in result) {
const { apps: appsOverviews } = result;
return appsOverviews.map((app) => {
const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app;
return {
...latest,
price,
pricingPlans,
purchaseType,
isEnterpriseOnly,
modifiedAt,
};
});
}
throw new Error('Apps not found');
}

public async getAppsOnBundle(bundleId: string): Promise<App[]> {
const { apps } = await APIClient.get(`/v1/apps/bundles/${bundleId}/apps`);
const { apps } = await APIClient.get(`/apps/bundles/${bundleId}/apps`);
return apps;
}

public async getAppsLanguages(): Promise<IAppLanguage> {
const { apps } = await APIClient.get('/v1/apps/languages');
const { apps } = await APIClient.get('/apps/languages');
return apps;
}

public async getApp(appId: string): Promise<App> {
const { app } = await APIClient.get(`/v1/apps/${appId}`);
const { app } = await APIClient.get(`/apps/${appId}` as any);
return app;
}

public async getAppFromMarketplace(appId: string, version: string): Promise<App> {
const { app } = await APIClient.get(`/v1/apps/${appId}`, {
marketplace: 'true',
version,
});
return app;
const result = await APIClient.get(
`/apps/${appId}` as any,
{
marketplace: 'true',
version,
} as any,
);
return result;
}

public async getLatestAppFromMarketplace(appId: string, version: string): Promise<App> {
const { app } = await APIClient.get(`/v1/apps/${appId}`, {
marketplace: 'true',
update: 'true',
appVersion: version,
});
const { app } = await APIClient.get(
`/apps/${appId}` as any,
{
marketplace: 'true',
update: 'true',
appVersion: version,
} as any,
);
return app;
}

public async getAppSettings(appId: string): Promise<ISettingsReturn> {
const { settings } = await APIClient.get(`/v1/apps/${appId}/settings`);
return settings;
}

public async setAppSettings(appId: string, settings: ISettingsPayload): Promise<ISettingsSetReturn> {
const { updated } = await APIClient.post(`/v1/apps/${appId}/settings`, undefined, { settings });
return updated;
public async setAppSettings(appId: string, settings: ISetting[]): Promise<void> {
await APIClient.post(`/apps/${appId}/settings`, { settings });
}

public async getAppApis(appId: string): Promise<IApiEndpointMetadata[]> {
const { apis } = await APIClient.get(`/v1/apps/${appId}/apis`);
const { apis } = await APIClient.get(`/apps/${appId}/apis`);
return apis;
}

public async getAppLanguages(appId: string): Promise<IAppStorageItem['languageContent']> {
const { languages } = await APIClient.get(`/v1/apps/${appId}/languages`);
const { languages } = await APIClient.get(`/apps/${appId}/languages`);
return languages;
}

public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise<IDeletedInstalledApp> {
const { app } = await APIClient.post('/v1/apps/', {
public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise<App> {
const { app } = await APIClient.post('/apps', {
appId,
marketplace: true,
version,
Expand All @@ -214,48 +221,48 @@ class AppClientOrchestrator {
}

public async updateApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise<App> {
const { app } = await APIClient.post(`/v1/apps/${appId}`, {
const result = (await (APIClient.post as any)(`/apps/${appId}` as any, {
appId,
marketplace: true,
version,
permissionsGranted,
});
return app;
}
})) as any;

public uninstallApp(appId: string): IDeletedInstalledApp {
return APIClient.delete(`apps/${appId}`);
}

public syncApp(appId: string): IAppSynced {
return APIClient.post(`/v1/apps/${appId}/sync`);
if ('app' in result) {
return result;
}
throw new Error('App not found');
}

public async setAppStatus(appId: string, status: AppStatus): Promise<string> {
const { status: effectiveStatus } = await APIClient.post(`/v1/apps/${appId}/status`, { status });
const { status: effectiveStatus } = await APIClient.post(`/apps/${appId}/status`, { status });
return effectiveStatus;
}

public enableApp(appId: string): Promise<string> {
return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED);
}

public disableApp(appId: string): Promise<string> {
return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED);
}

public buildExternalUrl(appId: string, purchaseType = 'buy', details = false): IAppExternalURL {
return APIClient.get('/v1/apps', {
public async buildExternalUrl(appId: string, purchaseType: 'buy' | 'subscription' = 'buy', details = false): Promise<IAppExternalURL> {
const result = await APIClient.get('/apps', {
buildExternalUrl: 'true',
appId,
purchaseType,
details,
details: `${details}`,
});

if ('url' in result) {
return result;
}
throw new Error('Failed to build external url');
}

public async getCategories(): Promise<ICategory[]> {
const categories = await APIClient.get('/v1/apps', { categories: 'true' });
return categories;
public async getCategories(): Promise<Serialized<ICategory>[]> {
const result = await APIClient.get('/apps', { categories: 'true' });
if ('categories' in result) {
return result.categories;
}
throw new Error('Categories not found');
}

public getUIHost(): RealAppsEngineUIHost {
Expand All @@ -267,7 +274,7 @@ export const Apps = new AppClientOrchestrator();

Meteor.startup(() => {
CachedCollectionManager.onLogin(() => {
Meteor.call('apps/is-enabled', (error: Error, isEnabled: boolean) => {
Meteor.call('/apps/is-enabled', (error: Error, isEnabled: boolean) => {
if (error) {
Apps.handleError(error);
return;
Expand All @@ -279,7 +286,7 @@ Meteor.startup(() => {
});

Tracker.autorun(() => {
const isEnabled = settings.get('Apps_Framework_enabled');
const isEnabled = settings.get('/Apps_Framework_enabled');
Apps.load(isEnabled);
});
});
8 changes: 3 additions & 5 deletions apps/meteor/client/lib/meteorCallWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,13 @@ function wrapMeteorDDPCalls(): void {
});
Meteor.connection.onMessage(_message);
};
const method = encodeURIComponent(message.method.replace(/\//g, ':'));

APIClient.post(
`/v1/${endpoint}/${encodeURIComponent(message.method.replace(/\//g, ':'))}` as Parameters<typeof APIClient.post>[0],
restParams as any,
)
APIClient.post(`/v1/${endpoint}/${method}`, restParams)
.then(({ message: _message }) => {
processResult(_message);
if (message.method === 'login') {
const parsedMessage = DDPCommon.parseDDP(_message) as { result?: { token?: string } };
const parsedMessage = DDPCommon.parseDDP(_message as any) as { result?: { token?: string } };
if (parsedMessage.result?.token) {
Meteor.loginWithToken(parsedMessage.result.token);
}
Expand Down
Loading