Skip to content
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
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type ErrorStatusCodes = Exclude<Exclude<Range<511>, Range<500>>, 509>;

export type SuccessResult<T, TStatusCode extends SuccessStatusCodes = 200> = {
statusCode: TStatusCode;
body: T extends object ? { success: true } & T : T;
body: T extends Record<string, unknown> ? { success: true } & T : T;
};

export type FailureResult<T, TStack = undefined, TErrorType = undefined, TErrorDetails = undefined> = {
Expand Down
143 changes: 82 additions & 61 deletions apps/meteor/app/api/server/v1/assets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { isAssetsUnsetAssetProps } from '@rocket.chat/rest-typings';
import {
ajv,
isAssetsUnsetAssetProps,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
validateBadRequestErrorResponse,
} from '@rocket.chat/rest-typings';

import { updateAuditedByUser } from '../../../../server/settings/lib/auditedSettingUpdates';
import { RocketChatAssets, refreshClients } from '../../../assets/server';
Expand All @@ -8,87 +14,102 @@ import { settings } from '../../../settings/server';
import { API } from '../api';
import { getUploadFormData } from '../lib/getUploadFormData';

API.v1.addRoute(
const successResponseSchema = ajv.compile<void>({
type: 'object',
properties: { success: { type: 'boolean', enum: [true] } },
required: ['success'],
additionalProperties: false,
});

API.v1.post(
'assets.setAsset',
{
authRequired: true,
permissionsRequired: ['manage-assets'],
response: {
200: successResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
{
async post() {
const asset = await getUploadFormData(
{
request: this.request,
},
{ field: 'asset', sizeLimit: settings.get('FileUpload_MaxFileSize') },
);
async function action() {
const asset = await getUploadFormData(
{
request: this.request,
},
{ field: 'asset', sizeLimit: settings.get('FileUpload_MaxFileSize') },
);

const { fileBuffer, fields, filename, mimetype } = asset;
const { fileBuffer, fields, filename, mimetype } = asset;

const { refreshAllClients, assetName: customName } = fields;
const { refreshAllClients, assetName: customName } = fields;

const assetName = customName || filename;
const assetsKeys = Object.keys(RocketChatAssets.assets);
const assetName = customName || filename;
const assetsKeys = Object.keys(RocketChatAssets.assets);

const isValidAsset = assetsKeys.includes(assetName);
if (!isValidAsset) {
throw new Error('Invalid asset');
}
const isValidAsset = assetsKeys.includes(assetName);
if (!isValidAsset) {
throw new Error('Invalid asset');
}

const { key, value } = await RocketChatAssets.setAssetWithBuffer(fileBuffer, mimetype, assetName);
const { key, value } = await RocketChatAssets.setAssetWithBuffer(fileBuffer, mimetype, assetName);

const { modifiedCount } = await updateAuditedByUser({
_id: this.userId,
username: this.user.username,
ip: this.requestIp,
useragent: this.request.headers.get('user-agent') || '',
})(Settings.updateValueById, key, value);
const { modifiedCount } = await updateAuditedByUser({
_id: this.userId,
username: this.user.username ?? '',
ip: this.requestIp ?? '',
useragent: this.request.headers.get('user-agent') ?? '',
})(Settings.updateValueById, key, value);

if (modifiedCount) {
void notifyOnSettingChangedById(key);
}
if (modifiedCount) {
void notifyOnSettingChangedById(key);
}

if (refreshAllClients) {
await refreshClients(this.userId);
}
if (refreshAllClients) {
await refreshClients(this.userId);
}

return API.v1.success();
},
return API.v1.success();
},
);

API.v1.addRoute(
API.v1.post(
'assets.unsetAsset',
{
authRequired: true,
validateParams: isAssetsUnsetAssetProps,
body: isAssetsUnsetAssetProps,
permissionsRequired: ['manage-assets'],
},
{
async post() {
const { assetName, refreshAllClients } = this.bodyParams;
const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName);
if (!isValidAsset) {
throw Error('Invalid asset');
}

const { key, value } = await RocketChatAssets.unsetAsset(assetName);

const { modifiedCount } = await updateAuditedByUser({
_id: this.userId,
username: this.user.username,
ip: this.requestIp,
useragent: this.request.headers.get('user-agent') || '',
})(Settings.updateValueById, key, value);

if (modifiedCount) {
void notifyOnSettingChangedById(key);
}

if (refreshAllClients) {
await refreshClients(this.userId);
}
return API.v1.success();
response: {
200: successResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
const { assetName, refreshAllClients } = this.bodyParams;
const isValidAsset = Object.keys(RocketChatAssets.assets).includes(assetName);
if (!isValidAsset) {
throw Error('Invalid asset');
}

const { key, value } = await RocketChatAssets.unsetAsset(assetName);

const { modifiedCount } = await updateAuditedByUser({
_id: this.userId,
username: this.user.username ?? '',
ip: this.requestIp ?? '',
useragent: this.request.headers.get('user-agent') ?? '',
})(Settings.updateValueById, key, value);

if (modifiedCount) {
void notifyOnSettingChangedById(key);
}

if (refreshAllClients) {
await refreshClients(this.userId);
}
return API.v1.success();
},
);
184 changes: 101 additions & 83 deletions apps/meteor/app/api/server/v1/autotranslate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage } from '@rocket.chat/core-typings';
import type { IMessage, ISupportedLanguage } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';
import {
ajv,
Expand All @@ -15,14 +15,51 @@ import { settings } from '../../../settings/server';
import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';

API.v1.addRoute(
'autotranslate.getSupportedLanguages',
{
authRequired: true,
validateParams: isAutotranslateGetSupportedLanguagesParamsGET,
type AutotranslateTranslateMessageParamsPOST = {
messageId: string;
targetLanguage?: string;
};

const AutotranslateTranslateMessageParamsPostSchema = {
type: 'object',
properties: {
messageId: {
type: 'string',
},
targetLanguage: {
type: 'string',
nullable: true,
},
},
{
async get() {
required: ['messageId'],
additionalProperties: false,
};

const isAutotranslateTranslateMessageParamsPOST = ajv.compile<AutotranslateTranslateMessageParamsPOST>(
AutotranslateTranslateMessageParamsPostSchema,
);

const autotranslateEndpoints = API.v1
.get(
'autotranslate.getSupportedLanguages',
{
authRequired: true,
query: isAutotranslateGetSupportedLanguagesParamsGET,
response: {
200: ajv.compile<{ languages: ISupportedLanguage[] }>({
type: 'object',
properties: {
languages: { type: 'array', items: { type: 'object' } },
success: { type: 'boolean', enum: [true] },
},
required: ['languages', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
if (!settings.get('AutoTranslate_Enabled')) {
return API.v1.failure('AutoTranslate is disabled.');
}
Expand All @@ -31,17 +68,24 @@ API.v1.addRoute(

return API.v1.success({ languages: languages || [] });
},
},
);

API.v1.addRoute(
'autotranslate.saveSettings',
{
authRequired: true,
validateParams: isAutotranslateSaveSettingsParamsPOST,
},
{
async post() {
)
.post(
'autotranslate.saveSettings',
{
authRequired: true,
body: isAutotranslateSaveSettingsParamsPOST,
response: {
200: ajv.compile<void>({
type: 'object',
properties: { success: { type: 'boolean', enum: [true] } },
required: ['success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { roomId, field, value, defaultLanguage } = this.bodyParams;
if (!settings.get('AutoTranslate_Enabled')) {
return API.v1.failure('AutoTranslate is disabled.');
Expand Down Expand Up @@ -70,74 +114,48 @@ API.v1.addRoute(

return API.v1.success();
},
},
);

type AutotranslateTranslateMessageParamsPOST = {
messageId: string;
targetLanguage?: string;
};

const AutotranslateTranslateMessageParamsPostSchema = {
type: 'object',
properties: {
messageId: {
type: 'string',
},
targetLanguage: {
type: 'string',
nullable: true,
)
.post(
'autotranslate.translateMessage',
{
authRequired: true,
body: isAutotranslateTranslateMessageParamsPOST,
response: {
200: ajv.compile<{ message: IMessage }>({
type: 'object',
properties: {
message: { $ref: '#/components/schemas/IMessage' },
success: { type: 'boolean', enum: [true] },
},
required: ['message', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
},
required: ['messageId'],
additionalProperties: false,
};

const isAutotranslateTranslateMessageParamsPOST = ajv.compile<AutotranslateTranslateMessageParamsPOST>(
AutotranslateTranslateMessageParamsPostSchema,
);

const autotranslateEndpoints = API.v1.post(
'autotranslate.translateMessage',
{
authRequired: true,
body: isAutotranslateTranslateMessageParamsPOST,
response: {
200: ajv.compile<{ message: IMessage }>({
type: 'object',
properties: {
message: { $ref: '#/components/schemas/IMessage' },
success: { type: 'boolean', enum: [true] },
},
required: ['message', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { messageId, targetLanguage } = this.bodyParams;
if (!settings.get('AutoTranslate_Enabled')) {
return API.v1.failure('AutoTranslate is disabled.');
}
if (!messageId) {
return API.v1.failure('The bodyParam "messageId" is required.');
}
const message = await Messages.findOneById(messageId);
if (!message) {
return API.v1.failure('Message not found.');
}
async function action() {
const { messageId, targetLanguage } = this.bodyParams;
if (!settings.get('AutoTranslate_Enabled')) {
return API.v1.failure('AutoTranslate is disabled.');
}
if (!messageId) {
return API.v1.failure('The bodyParam "messageId" is required.');
}
const message = await Messages.findOneById(messageId);
if (!message) {
return API.v1.failure('Message not found.');
}

const translatedMessage = await translateMessage(targetLanguage, message);
const translatedMessage = await translateMessage(targetLanguage, message);

if (!translatedMessage) {
return API.v1.failure('Failed to translate message.');
}
if (!translatedMessage) {
return API.v1.failure('Failed to translate message.');
}

return API.v1.success({ message: translatedMessage });
},
);
return API.v1.success({ message: translatedMessage });
},
);

type AutotranslateEndpoints = ExtractRoutesFromAPI<typeof autotranslateEndpoints>;

Expand Down
Loading
Loading