snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import fs from 'node:fs';
|
||||
import type { Readable } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
|
||||
import { Import } from '@rocket.chat/core-services';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { Importers } from '../../lib/import';
|
||||
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
|
||||
import { SystemLogger } from '../../lib/logger/system';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
async function getHttpFileStream(fileUrl: string): Promise<Readable> {
|
||||
const response = await fetch(fileUrl, {
|
||||
ignoreSsrfValidation: false,
|
||||
allowList: settings.get<string>('SSRF_Allowlist'),
|
||||
});
|
||||
|
||||
const body = response.body as Readable;
|
||||
if (!response.ok) {
|
||||
body.resume();
|
||||
throw new Error(`Unexpected response status ${response.status}`);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function copyLocalFile(filePath: fs.PathLike, writeStream: fs.WriteStream): void {
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
|
||||
export const executeDownloadPublicImportFile = async (userId: IUser['_id'], fileUrl: string, importerKey: string): Promise<void> => {
|
||||
const importer = Importers.get(importerKey);
|
||||
const isUrl = fileUrl.startsWith('http');
|
||||
if (!importer) {
|
||||
throw new Meteor.Error(
|
||||
'error-importer-not-defined',
|
||||
`The importer (${importerKey}) has no import class defined.`,
|
||||
'downloadImportFile',
|
||||
);
|
||||
}
|
||||
// Check if it's a valid url or path before creating a new import record
|
||||
if (!isUrl && !fs.existsSync(fileUrl)) {
|
||||
throw new Meteor.Error('error-import-file-missing', fileUrl, 'downloadPublicImportFile');
|
||||
}
|
||||
|
||||
const operation = await Import.newOperation(userId, importer.name, importer.key);
|
||||
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
|
||||
|
||||
const oldFileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1).split('?')[0];
|
||||
const date = new Date();
|
||||
const dateStr = `${date.getUTCFullYear()}${date.getUTCMonth()}${date.getUTCDate()}${date.getUTCHours()}${date.getUTCMinutes()}${date.getUTCSeconds()}`;
|
||||
const newFileName = `${dateStr}_${userId}_${oldFileName}`;
|
||||
|
||||
// Store the file name on the imports collection
|
||||
await instance.startFileUpload(newFileName);
|
||||
await instance.updateProgress(ProgressStep.DOWNLOADING_FILE);
|
||||
|
||||
const writeStream = RocketChatImportFileInstance.createWriteStream(newFileName);
|
||||
let errorProgressUpdate: Promise<unknown> | undefined;
|
||||
const markImportAsFailed = (): Promise<unknown> => {
|
||||
errorProgressUpdate ??= instance.updateProgress(ProgressStep.ERROR).catch((error) => {
|
||||
SystemLogger.error({ msg: 'Failed to update import progress to ERROR', err: error });
|
||||
});
|
||||
return errorProgressUpdate;
|
||||
};
|
||||
|
||||
writeStream.on('error', () => {
|
||||
void markImportAsFailed();
|
||||
});
|
||||
|
||||
let readStream: Readable | undefined;
|
||||
if (isUrl) {
|
||||
try {
|
||||
readStream = await getHttpFileStream(fileUrl);
|
||||
} catch (error) {
|
||||
writeStream.destroy();
|
||||
await markImportAsFailed();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
writeStream.on('finish', () => {
|
||||
void instance.updateProgress(ProgressStep.FILE_LOADED);
|
||||
});
|
||||
|
||||
if (readStream) {
|
||||
void pipeline(readStream, writeStream).catch(() => markImportAsFailed());
|
||||
return;
|
||||
}
|
||||
|
||||
// If the url is actually a folder path on the current machine, skip moving it to the file store
|
||||
if (fs.statSync(fileUrl).isDirectory()) {
|
||||
await instance.updateRecord({ file: fileUrl });
|
||||
await instance.updateProgress(ProgressStep.FILE_LOADED);
|
||||
return;
|
||||
}
|
||||
|
||||
copyLocalFile(fileUrl, writeStream);
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
downloadPublicImportFile(fileUrl: string, importerKey: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async downloadPublicImportFile(fileUrl: string, importerKey: string) {
|
||||
methodDeprecationLogger.method('downloadPublicImportFile', '9.0.0', '/v1/downloadPublicImportFile');
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'downloadPublicImportFile');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'run-import'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'downloadPublicImportFile');
|
||||
}
|
||||
|
||||
await executeDownloadPublicImportFile(userId, fileUrl, importerKey);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { IImportProgress, IImporterSelection } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Imports } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { Importers } from '../../lib/import';
|
||||
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
|
||||
|
||||
export const executeGetImportFileData = async (): Promise<IImporterSelection | { waiting: true }> => {
|
||||
const operation = await Imports.findLastImport();
|
||||
if (!operation) {
|
||||
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'getImportFileData');
|
||||
}
|
||||
|
||||
const { importerKey } = operation;
|
||||
|
||||
const importer = Importers.get(importerKey);
|
||||
if (!importer) {
|
||||
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'getImportFileData');
|
||||
}
|
||||
|
||||
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
|
||||
|
||||
const waitingSteps: IImportProgress['step'][] = [
|
||||
ProgressStep.DOWNLOADING_FILE,
|
||||
ProgressStep.PREPARING_CHANNELS,
|
||||
ProgressStep.PREPARING_MESSAGES,
|
||||
ProgressStep.PREPARING_USERS,
|
||||
ProgressStep.PREPARING_CONTACTS,
|
||||
ProgressStep.PREPARING_STARTED,
|
||||
];
|
||||
|
||||
if (waitingSteps.indexOf(instance.progress.step) >= 0) {
|
||||
if (instance.importRecord?.valid) {
|
||||
return { waiting: true };
|
||||
}
|
||||
throw new Meteor.Error('error-import-operation-invalid', 'Invalid Import Operation', 'getImportFileData');
|
||||
}
|
||||
|
||||
const readySteps: IImportProgress['step'][] = [
|
||||
ProgressStep.USER_SELECTION,
|
||||
ProgressStep.DONE,
|
||||
ProgressStep.CANCELLED,
|
||||
ProgressStep.ERROR,
|
||||
];
|
||||
|
||||
if (readySteps.indexOf(instance.progress.step) >= 0) {
|
||||
return instance.buildSelection();
|
||||
}
|
||||
|
||||
const fileName = instance.importRecord.file;
|
||||
if (fileName) {
|
||||
const fullFilePath = fs.existsSync(fileName) ? fileName : path.join(RocketChatImportFileInstance.absolutePath, fileName);
|
||||
await instance.prepareUsingLocalFile(fullFilePath);
|
||||
}
|
||||
|
||||
return instance.buildSelection();
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
getImportFileData(): IImporterSelection | { waiting: true };
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async getImportFileData() {
|
||||
methodDeprecationLogger.method('getImportFileData', '9.0.0', '/v1/getImportFileData');
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getImportFileData');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'run-import'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'getImportFileData');
|
||||
}
|
||||
|
||||
return executeGetImportFileData();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { IImportProgress } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Imports } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { Importers } from '../../lib/import';
|
||||
|
||||
export const executeGetImportProgress = async (): Promise<IImportProgress> => {
|
||||
const operation = await Imports.findLastImport();
|
||||
if (!operation) {
|
||||
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'getImportProgress');
|
||||
}
|
||||
|
||||
const { importerKey } = operation;
|
||||
const importer = Importers.get(importerKey);
|
||||
if (!importer) {
|
||||
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'getImportProgress');
|
||||
}
|
||||
|
||||
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
|
||||
|
||||
return instance.getProgress();
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
getImportProgress(): IImportProgress;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async getImportProgress() {
|
||||
methodDeprecationLogger.method('getImportProgress', '9.0.0', '/v1/getImportProgress');
|
||||
const userId = Meteor.userId();
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getImportProgress');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'run-import'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'setupImporter');
|
||||
}
|
||||
|
||||
return executeGetImportProgress();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { IImport } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Imports } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
|
||||
export const executeGetLatestImportOperations = async () => {
|
||||
const data = Imports.find(
|
||||
{},
|
||||
{
|
||||
sort: { _updatedAt: -1 },
|
||||
limit: 20,
|
||||
},
|
||||
);
|
||||
|
||||
return data.toArray();
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
getLatestImportOperations(): IImport[];
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async getLatestImportOperations() {
|
||||
methodDeprecationLogger.method('getLatestImportOperations', '9.0.0', '/v1/getLatestImportOperations');
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getLatestImportOperations');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'view-import-operations'))) {
|
||||
throw new Meteor.Error('not_authorized', 'User not authorized', 'getLatestImportOperations');
|
||||
}
|
||||
|
||||
return executeGetLatestImportOperations();
|
||||
},
|
||||
});
|
||||
54
apps/meteor/server/meteor-methods/import/startImport.ts
Normal file
54
apps/meteor/server/meteor-methods/import/startImport.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Imports } from '@rocket.chat/models';
|
||||
import { isStartImportParamsPOST, type StartImportParamsPOST } from '@rocket.chat/rest-typings';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { Importers } from '../../lib/import';
|
||||
|
||||
export const executeStartImport = async ({ input }: StartImportParamsPOST, startedByUserId: IUser['_id']) => {
|
||||
const operation = await Imports.findLastImport();
|
||||
if (!operation) {
|
||||
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'startImport');
|
||||
}
|
||||
|
||||
const { importerKey } = operation;
|
||||
const importer = Importers.get(importerKey);
|
||||
if (!importer) {
|
||||
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'startImport');
|
||||
}
|
||||
|
||||
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
|
||||
|
||||
await instance.startImport(input, startedByUserId);
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
startImport(params: StartImportParamsPOST): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async startImport({ input }: StartImportParamsPOST) {
|
||||
methodDeprecationLogger.method('startImport', '9.0.0', '/v1/startImport');
|
||||
if (!input || typeof input !== 'object' || !isStartImportParamsPOST({ input })) {
|
||||
throw new Meteor.Error(`Invalid Selection data provided to the importer.`);
|
||||
}
|
||||
|
||||
const userId = Meteor.userId();
|
||||
// Takes name and object with users / channels selected to import
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'startImport');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'run-import'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'startImport');
|
||||
}
|
||||
|
||||
return executeStartImport({ input }, userId);
|
||||
},
|
||||
});
|
||||
81
apps/meteor/server/meteor-methods/import/uploadImportFile.ts
Normal file
81
apps/meteor/server/meteor-methods/import/uploadImportFile.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Import } from '@rocket.chat/core-services';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { Importers } from '../../lib/import';
|
||||
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
|
||||
import { RocketChatFile } from '../../lib/media/file';
|
||||
|
||||
export const executeUploadImportFile = async (
|
||||
userId: IUser['_id'],
|
||||
binaryContent: string,
|
||||
contentType: string,
|
||||
fileName: string,
|
||||
importerKey: string,
|
||||
): Promise<void> => {
|
||||
const importer = Importers.get(importerKey);
|
||||
if (!importer) {
|
||||
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'uploadImportFile');
|
||||
}
|
||||
|
||||
const operation = await Import.newOperation(userId, importer.name, importer.key);
|
||||
|
||||
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
|
||||
|
||||
const date = new Date();
|
||||
const dateStr = `${date.getUTCFullYear()}${date.getUTCMonth()}${date.getUTCDate()}${date.getUTCHours()}${date.getUTCMinutes()}${date.getUTCSeconds()}`;
|
||||
const newFileName = `${dateStr}_${userId}_${fileName}`;
|
||||
|
||||
// Store the file name and content type on the imports collection
|
||||
await instance.startFileUpload(newFileName, contentType);
|
||||
|
||||
// Save the file on the File Store
|
||||
const file = Buffer.from(binaryContent, 'base64');
|
||||
const readStream = RocketChatFile.bufferToStream(file);
|
||||
const writeStream = RocketChatImportFileInstance.createWriteStream(newFileName, contentType);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
writeStream.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
writeStream.on('error', (e: Error) => {
|
||||
reject(e);
|
||||
});
|
||||
|
||||
readStream.pipe(writeStream);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
await instance.updateProgress(ProgressStep.FILE_LOADED);
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
uploadImportFile(binaryContent: string, contentType: string, fileName: string, importerKey: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
async uploadImportFile(binaryContent, contentType, fileName, importerKey) {
|
||||
methodDeprecationLogger.method('uploadImportFile', '9.0.0', '/v1/uploadImportFile');
|
||||
const userId = Meteor.userId();
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'uploadImportFile');
|
||||
}
|
||||
|
||||
if (!(await hasPermissionAsync(userId, 'run-import'))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'uploadImportFile');
|
||||
}
|
||||
|
||||
await executeUploadImportFile(userId, binaryContent, contentType, fileName, importerKey);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user