|
@@ -1,13 +1,13 @@
|
|
|
-import { createReadStream } from 'fs';
|
|
|
|
|
-import path from 'path';
|
|
|
|
|
-
|
|
|
|
|
import { SCOPE } from '@growi/core/dist/interfaces';
|
|
import { SCOPE } from '@growi/core/dist/interfaces';
|
|
|
import { ErrorV3 } from '@growi/core/dist/models';
|
|
import { ErrorV3 } from '@growi/core/dist/models';
|
|
|
import type { NextFunction, Request, Router } from 'express';
|
|
import type { NextFunction, Request, Router } from 'express';
|
|
|
import express from 'express';
|
|
import express from 'express';
|
|
|
import { body } from 'express-validator';
|
|
import { body } from 'express-validator';
|
|
|
|
|
+import { createReadStream } from 'fs';
|
|
|
import multer from 'multer';
|
|
import multer from 'multer';
|
|
|
|
|
+import path from 'path';
|
|
|
|
|
|
|
|
|
|
+import type { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option';
|
|
|
import { accessTokenParser } from '~/server/middlewares/access-token-parser';
|
|
import { accessTokenParser } from '~/server/middlewares/access-token-parser';
|
|
|
import { isG2GTransferError } from '~/server/models/vo/g2g-transfer-error';
|
|
import { isG2GTransferError } from '~/server/models/vo/g2g-transfer-error';
|
|
|
import { configManager } from '~/server/service/config-manager';
|
|
import { configManager } from '~/server/service/config-manager';
|
|
@@ -19,15 +19,13 @@ import { getImportService } from '~/server/service/import';
|
|
|
import loggerFactory from '~/utils/logger';
|
|
import loggerFactory from '~/utils/logger';
|
|
|
import { TransferKey } from '~/utils/vo/transfer-key';
|
|
import { TransferKey } from '~/utils/vo/transfer-key';
|
|
|
|
|
|
|
|
-
|
|
|
|
|
import type Crowi from '../../crowi';
|
|
import type Crowi from '../../crowi';
|
|
|
import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
|
|
import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
|
|
|
import { Attachment } from '../../models/attachment';
|
|
import { Attachment } from '../../models/attachment';
|
|
|
-
|
|
|
|
|
import type { ApiV3Response } from './interfaces/apiv3-response';
|
|
import type { ApiV3Response } from './interfaces/apiv3-response';
|
|
|
|
|
|
|
|
interface AuthorizedRequest extends Request {
|
|
interface AuthorizedRequest extends Request {
|
|
|
- user?: any
|
|
|
|
|
|
|
+ user?: any;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const logger = loggerFactory('growi:routes:apiv3:transfer');
|
|
const logger = loggerFactory('growi:routes:apiv3:transfer');
|
|
@@ -76,20 +74,27 @@ const validator = {
|
|
|
* type: string
|
|
* type: string
|
|
|
* containerName:
|
|
* containerName:
|
|
|
* type: string
|
|
* type: string
|
|
|
-*/
|
|
|
|
|
|
|
+ */
|
|
|
/*
|
|
/*
|
|
|
* Routes
|
|
* Routes
|
|
|
*/
|
|
*/
|
|
|
module.exports = (crowi: Crowi): Router => {
|
|
module.exports = (crowi: Crowi): Router => {
|
|
|
const {
|
|
const {
|
|
|
- g2gTransferPusherService, g2gTransferReceiverService,
|
|
|
|
|
|
|
+ g2gTransferPusherService,
|
|
|
|
|
+ g2gTransferReceiverService,
|
|
|
growiBridgeService,
|
|
growiBridgeService,
|
|
|
} = crowi;
|
|
} = crowi;
|
|
|
|
|
|
|
|
const importService = getImportService();
|
|
const importService = getImportService();
|
|
|
|
|
|
|
|
- if (g2gTransferPusherService == null || g2gTransferReceiverService == null || exportService == null || importService == null
|
|
|
|
|
- || growiBridgeService == null || configManager == null) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ g2gTransferPusherService == null ||
|
|
|
|
|
+ g2gTransferReceiverService == null ||
|
|
|
|
|
+ exportService == null ||
|
|
|
|
|
+ importService == null ||
|
|
|
|
|
+ growiBridgeService == null ||
|
|
|
|
|
+ configManager == null
|
|
|
|
|
+ ) {
|
|
|
throw Error('GROWI is not ready for g2g transfer');
|
|
throw Error('GROWI is not ready for g2g transfer');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -126,10 +131,16 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
const isInstalled = configManager.getConfig('app:installed');
|
|
const isInstalled = configManager.getConfig('app:installed');
|
|
|
|
|
|
|
|
const adminRequired = require('../../middlewares/admin-required')(crowi);
|
|
const adminRequired = require('../../middlewares/admin-required')(crowi);
|
|
|
- const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
|
|
|
|
|
|
|
+ const loginRequiredStrictly = require('../../middlewares/login-required')(
|
|
|
|
|
+ crowi,
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
// Middleware
|
|
// Middleware
|
|
|
- const adminRequiredIfInstalled = (req: Request, res: ApiV3Response, next: NextFunction) => {
|
|
|
|
|
|
|
+ const adminRequiredIfInstalled = (
|
|
|
|
|
+ req: Request,
|
|
|
|
|
+ res: ApiV3Response,
|
|
|
|
|
+ next: NextFunction,
|
|
|
|
|
+ ) => {
|
|
|
if (!isInstalled) {
|
|
if (!isInstalled) {
|
|
|
next();
|
|
next();
|
|
|
return;
|
|
return;
|
|
@@ -139,29 +150,47 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Middleware
|
|
// Middleware
|
|
|
- const appSiteUrlRequiredIfNotInstalled = (req: Request, res: ApiV3Response, next: NextFunction) => {
|
|
|
|
|
|
|
+ const appSiteUrlRequiredIfNotInstalled = (
|
|
|
|
|
+ req: Request,
|
|
|
|
|
+ res: ApiV3Response,
|
|
|
|
|
+ next: NextFunction,
|
|
|
|
|
+ ) => {
|
|
|
if (!isInstalled && req.body.appSiteUrl != null) {
|
|
if (!isInstalled && req.body.appSiteUrl != null) {
|
|
|
next();
|
|
next();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (configManager.getConfig('app:siteUrl') != null || req.body.appSiteUrl != null) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ configManager.getConfig('app:siteUrl') != null ||
|
|
|
|
|
+ req.body.appSiteUrl != null
|
|
|
|
|
+ ) {
|
|
|
next();
|
|
next();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Body param "appSiteUrl" is required when GROWI is NOT installed yet'), 400);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Body param "appSiteUrl" is required when GROWI is NOT installed yet',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 400,
|
|
|
|
|
+ );
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Local middleware to check if key is valid or not
|
|
// Local middleware to check if key is valid or not
|
|
|
- const validateTransferKey = async(req: Request, res: ApiV3Response, next: NextFunction) => {
|
|
|
|
|
|
|
+ const validateTransferKey = async (
|
|
|
|
|
+ req: Request,
|
|
|
|
|
+ res: ApiV3Response,
|
|
|
|
|
+ next: NextFunction,
|
|
|
|
|
+ ) => {
|
|
|
const transferKey = req.headers[X_GROWI_TRANSFER_KEY_HEADER_NAME] as string;
|
|
const transferKey = req.headers[X_GROWI_TRANSFER_KEY_HEADER_NAME] as string;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
await g2gTransferReceiverService.validateTransferKey(transferKey);
|
|
await g2gTransferReceiverService.validateTransferKey(transferKey);
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Invalid transfer key', 'invalid_transfer_key'), 403);
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3('Invalid transfer key', 'invalid_transfer_key'),
|
|
|
|
|
+ 403,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
next();
|
|
next();
|
|
@@ -200,10 +229,14 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* type: number
|
|
* type: number
|
|
|
* description: The size of the file
|
|
* description: The size of the file
|
|
|
*/
|
|
*/
|
|
|
- receiveRouter.get('/files', validateTransferKey, async(req: Request, res: ApiV3Response) => {
|
|
|
|
|
- const files = await crowi.fileUploadService.listFiles();
|
|
|
|
|
- return res.apiv3({ files });
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ receiveRouter.get(
|
|
|
|
|
+ '/files',
|
|
|
|
|
+ validateTransferKey,
|
|
|
|
|
+ async (req: Request, res: ApiV3Response) => {
|
|
|
|
|
+ const files = await crowi.fileUploadService.listFiles();
|
|
|
|
|
+ return res.apiv3({ files });
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @swagger
|
|
* @swagger
|
|
@@ -251,88 +284,122 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* type: string
|
|
* type: string
|
|
|
* description: The message of the result
|
|
* description: The message of the result
|
|
|
*/
|
|
*/
|
|
|
- receiveRouter.post('/', validateTransferKey, uploads.single('transferDataZipFile'), async(req: Request & { file: any; }, res: ApiV3Response) => {
|
|
|
|
|
- const { file } = req;
|
|
|
|
|
- const {
|
|
|
|
|
- collections: strCollections,
|
|
|
|
|
- optionsMap: strOptionsMap,
|
|
|
|
|
- operatorUserId,
|
|
|
|
|
- uploadConfigs: strUploadConfigs,
|
|
|
|
|
- } = req.body;
|
|
|
|
|
-
|
|
|
|
|
- /*
|
|
|
|
|
- * parse multipart form data
|
|
|
|
|
- */
|
|
|
|
|
- let collections;
|
|
|
|
|
- let optionsMap;
|
|
|
|
|
- let sourceGROWIUploadConfigs;
|
|
|
|
|
- try {
|
|
|
|
|
- collections = JSON.parse(strCollections);
|
|
|
|
|
- optionsMap = JSON.parse(strOptionsMap);
|
|
|
|
|
- sourceGROWIUploadConfigs = JSON.parse(strUploadConfigs);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to parse request body.', 'parse_failed'), 500);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ receiveRouter.post(
|
|
|
|
|
+ '/',
|
|
|
|
|
+ validateTransferKey,
|
|
|
|
|
+ uploads.single('transferDataZipFile'),
|
|
|
|
|
+ async (req: Request & { file: any }, res: ApiV3Response) => {
|
|
|
|
|
+ const { file } = req;
|
|
|
|
|
+ const {
|
|
|
|
|
+ collections: strCollections,
|
|
|
|
|
+ optionsMap: strOptionsMap,
|
|
|
|
|
+ operatorUserId,
|
|
|
|
|
+ uploadConfigs: strUploadConfigs,
|
|
|
|
|
+ } = req.body;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * parse multipart form data
|
|
|
|
|
+ */
|
|
|
|
|
+ let collections: string[];
|
|
|
|
|
+ let optionsMap: { [key: string]: GrowiArchiveImportOption };
|
|
|
|
|
+ let sourceGROWIUploadConfigs: any;
|
|
|
|
|
+ try {
|
|
|
|
|
+ collections = JSON.parse(strCollections);
|
|
|
|
|
+ optionsMap = JSON.parse(strOptionsMap);
|
|
|
|
|
+ sourceGROWIUploadConfigs = JSON.parse(strUploadConfigs);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3('Failed to parse request body.', 'parse_failed'),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- * unzip and parse
|
|
|
|
|
- */
|
|
|
|
|
- let meta;
|
|
|
|
|
- let innerFileStats;
|
|
|
|
|
- try {
|
|
|
|
|
- const zipFile = importService.getFile(file.filename);
|
|
|
|
|
- await importService.unzip(zipFile);
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * unzip and parse
|
|
|
|
|
+ */
|
|
|
|
|
+ let meta: object | undefined;
|
|
|
|
|
+ let innerFileStats: {
|
|
|
|
|
+ fileName: string;
|
|
|
|
|
+ collectionName: string;
|
|
|
|
|
+ size: number;
|
|
|
|
|
+ }[];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const zipFile = importService.getFile(file.filename);
|
|
|
|
|
+ await importService.unzip(zipFile);
|
|
|
|
|
|
|
|
- const zipFileStat = await growiBridgeService.parseZipFile(zipFile);
|
|
|
|
|
- innerFileStats = zipFileStat?.innerFileStats;
|
|
|
|
|
- meta = zipFileStat?.meta;
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to validate transfer data file.', 'validation_failed'), 500);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const zipFileStat = await growiBridgeService.parseZipFile(zipFile);
|
|
|
|
|
+ innerFileStats = zipFileStat?.innerFileStats ?? [];
|
|
|
|
|
+ meta = zipFileStat?.meta;
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Failed to validate transfer data file.',
|
|
|
|
|
+ 'validation_failed',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- * validate meta.json
|
|
|
|
|
- */
|
|
|
|
|
- try {
|
|
|
|
|
- importService.validate(meta);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
- return res.apiv3Err(
|
|
|
|
|
- new ErrorV3(
|
|
|
|
|
- 'The version of this GROWI and the uploaded GROWI data are not the same',
|
|
|
|
|
- 'version_incompatible',
|
|
|
|
|
- ),
|
|
|
|
|
- 500,
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * validate meta.json
|
|
|
|
|
+ */
|
|
|
|
|
+ try {
|
|
|
|
|
+ importService.validate(meta);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'The version of this GROWI and the uploaded GROWI data are not the same',
|
|
|
|
|
+ 'version_incompatible',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- * generate maps of ImportSettings to import
|
|
|
|
|
- */
|
|
|
|
|
- let importSettingsMap: Map<string, ImportSettings>;
|
|
|
|
|
- try {
|
|
|
|
|
- importSettingsMap = g2gTransferReceiverService.getImportSettingMap(innerFileStats, optionsMap, operatorUserId);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Import settings are invalid. See GROWI docs about details.', 'import_settings_invalid'));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * generate maps of ImportSettings to import
|
|
|
|
|
+ */
|
|
|
|
|
+ let importSettingsMap: Map<string, ImportSettings>;
|
|
|
|
|
+ try {
|
|
|
|
|
+ importSettingsMap = g2gTransferReceiverService.getImportSettingMap(
|
|
|
|
|
+ innerFileStats,
|
|
|
|
|
+ optionsMap,
|
|
|
|
|
+ operatorUserId,
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Import settings are invalid. See GROWI docs about details.',
|
|
|
|
|
+ 'import_settings_invalid',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- await g2gTransferReceiverService.importCollections(collections, importSettingsMap, sourceGROWIUploadConfigs);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to import MongoDB collections', 'mongo_collection_import_failure'), 500);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ await g2gTransferReceiverService.importCollections(
|
|
|
|
|
+ collections,
|
|
|
|
|
+ importSettingsMap,
|
|
|
|
|
+ sourceGROWIUploadConfigs,
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Failed to import MongoDB collections',
|
|
|
|
|
+ 'mongo_collection_import_failure',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return res.apiv3({ message: 'Successfully started to receive transfer data.' });
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ return res.apiv3({
|
|
|
|
|
+ message: 'Successfully started to receive transfer data.',
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @swagger
|
|
* @swagger
|
|
@@ -370,54 +437,101 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* description: The message of the result
|
|
* description: The message of the result
|
|
|
*/
|
|
*/
|
|
|
// This endpoint uses multer's MemoryStorage since the received data should be persisted directly on attachment storage.
|
|
// This endpoint uses multer's MemoryStorage since the received data should be persisted directly on attachment storage.
|
|
|
- receiveRouter.post('/attachment', validateTransferKey, uploadsForAttachment.single('content'),
|
|
|
|
|
- async(req: Request & { file: any; }, res: ApiV3Response) => {
|
|
|
|
|
|
|
+ receiveRouter.post(
|
|
|
|
|
+ '/attachment',
|
|
|
|
|
+ validateTransferKey,
|
|
|
|
|
+ uploadsForAttachment.single('content'),
|
|
|
|
|
+ async (req: Request & { file: any }, res: ApiV3Response) => {
|
|
|
const { file } = req;
|
|
const { file } = req;
|
|
|
const { attachmentMetadata } = req.body;
|
|
const { attachmentMetadata } = req.body;
|
|
|
|
|
|
|
|
- let attachmentMap;
|
|
|
|
|
|
|
+ let attachmentMap: { fileName: any; fileSize: any };
|
|
|
try {
|
|
try {
|
|
|
attachmentMap = JSON.parse(attachmentMetadata);
|
|
attachmentMap = JSON.parse(attachmentMetadata);
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to parse body.', 'parse_failed'), 500);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3('Failed to parse body.', 'parse_failed'),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const { fileName, fileSize } = attachmentMap;
|
|
const { fileName, fileSize } = attachmentMap;
|
|
|
- if (typeof fileName !== 'string' || fileName.length === 0 || fileName.length > 256) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ typeof fileName !== 'string' ||
|
|
|
|
|
+ fileName.length === 0 ||
|
|
|
|
|
+ fileName.length > 256
|
|
|
|
|
+ ) {
|
|
|
logger.warn('Invalid fileName in attachment metadata.', { fileName });
|
|
logger.warn('Invalid fileName in attachment metadata.', { fileName });
|
|
|
- return res.apiv3Err(new ErrorV3('Invalid fileName in attachment metadata.', 'invalid_metadata'), 400);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Invalid fileName in attachment metadata.',
|
|
|
|
|
+ 'invalid_metadata',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 400,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
- if (typeof fileSize !== 'number' || !Number.isInteger(fileSize) || fileSize < 0) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ typeof fileSize !== 'number' ||
|
|
|
|
|
+ !Number.isInteger(fileSize) ||
|
|
|
|
|
+ fileSize < 0
|
|
|
|
|
+ ) {
|
|
|
logger.warn('Invalid fileSize in attachment metadata.', { fileSize });
|
|
logger.warn('Invalid fileSize in attachment metadata.', { fileSize });
|
|
|
- return res.apiv3Err(new ErrorV3('Invalid fileSize in attachment metadata.', 'invalid_metadata'), 400);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Invalid fileSize in attachment metadata.',
|
|
|
|
|
+ 'invalid_metadata',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 400,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
const count = await Attachment.countDocuments({ fileName, fileSize });
|
|
const count = await Attachment.countDocuments({ fileName, fileSize });
|
|
|
if (count === 0) {
|
|
if (count === 0) {
|
|
|
- logger.warn('Attachment not found in collection.', { fileName, fileSize });
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Attachment not found in collection.', 'attachment_not_found'), 404);
|
|
|
|
|
|
|
+ logger.warn('Attachment not found in collection.', {
|
|
|
|
|
+ fileName,
|
|
|
|
|
+ fileSize,
|
|
|
|
|
+ });
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Attachment not found in collection.',
|
|
|
|
|
+ 'attachment_not_found',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 404,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to check attachment existence.', 'attachment_check_failed'), 500);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Failed to check attachment existence.',
|
|
|
|
|
+ 'attachment_check_failed',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const fileStream = createReadStream(file.path, {
|
|
const fileStream = createReadStream(file.path, {
|
|
|
- flags: 'r', mode: 0o666, autoClose: true,
|
|
|
|
|
|
|
+ flags: 'r',
|
|
|
|
|
+ mode: 0o666,
|
|
|
|
|
+ autoClose: true,
|
|
|
});
|
|
});
|
|
|
try {
|
|
try {
|
|
|
- await g2gTransferReceiverService.receiveAttachment(fileStream, attachmentMap);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ await g2gTransferReceiverService.receiveAttachment(
|
|
|
|
|
+ fileStream,
|
|
|
|
|
+ attachmentMap,
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to upload.', 'upload_failed'), 500);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3('Failed to upload.', 'upload_failed'),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return res.apiv3({ message: 'Successfully imported attached file.' });
|
|
return res.apiv3({ message: 'Successfully imported attached file.' });
|
|
|
- });
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @swagger
|
|
* @swagger
|
|
@@ -439,23 +553,32 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* growiInfo:
|
|
* growiInfo:
|
|
|
* $ref: '#/components/schemas/GrowiInfo'
|
|
* $ref: '#/components/schemas/GrowiInfo'
|
|
|
*/
|
|
*/
|
|
|
- receiveRouter.get('/growi-info', validateTransferKey, async(req: Request, res: ApiV3Response) => {
|
|
|
|
|
- let growiInfo: IDataGROWIInfo;
|
|
|
|
|
- try {
|
|
|
|
|
- growiInfo = await g2gTransferReceiverService.answerGROWIInfo();
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error(err);
|
|
|
|
|
|
|
+ receiveRouter.get(
|
|
|
|
|
+ '/growi-info',
|
|
|
|
|
+ validateTransferKey,
|
|
|
|
|
+ async (req: Request, res: ApiV3Response) => {
|
|
|
|
|
+ let growiInfo: IDataGROWIInfo;
|
|
|
|
|
+ try {
|
|
|
|
|
+ growiInfo = await g2gTransferReceiverService.answerGROWIInfo();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
|
|
|
- if (!isG2GTransferError(err)) {
|
|
|
|
|
- return res.apiv3Err(new ErrorV3('Failed to prepare GROWI info', 'failed_to_prepare_growi_info'), 500);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!isG2GTransferError(err)) {
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Failed to prepare GROWI info',
|
|
|
|
|
+ 'failed_to_prepare_growi_info',
|
|
|
|
|
+ ),
|
|
|
|
|
+ 500,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return res.apiv3Err(new ErrorV3(err.message, err.code), 500);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return res.apiv3Err(new ErrorV3(err.message, err.code), 500);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return res.apiv3({ growiInfo });
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ return res.apiv3({ growiInfo });
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @swagger
|
|
* @swagger
|
|
@@ -489,32 +612,46 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* type: string
|
|
* type: string
|
|
|
* description: The transfer key
|
|
* description: The transfer key
|
|
|
*/
|
|
*/
|
|
|
- receiveRouter.post('/generate-key',
|
|
|
|
|
|
|
+ receiveRouter.post(
|
|
|
|
|
+ '/generate-key',
|
|
|
accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
|
|
accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
|
|
|
- adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
|
|
|
|
|
- const appSiteUrl = req.body.appSiteUrl ?? configManager.getConfig('app:siteUrl');
|
|
|
|
|
|
|
+ adminRequiredIfInstalled,
|
|
|
|
|
+ appSiteUrlRequiredIfNotInstalled,
|
|
|
|
|
+ async (req: Request, res: ApiV3Response) => {
|
|
|
|
|
+ const appSiteUrl =
|
|
|
|
|
+ req.body.appSiteUrl ?? configManager.getConfig('app:siteUrl');
|
|
|
|
|
|
|
|
let appSiteUrlOrigin: string;
|
|
let appSiteUrlOrigin: string;
|
|
|
try {
|
|
try {
|
|
|
appSiteUrlOrigin = new URL(appSiteUrl).origin;
|
|
appSiteUrlOrigin = new URL(appSiteUrl).origin;
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('appSiteUrl may be wrong', 'failed_to_generate_key_string'));
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'appSiteUrl may be wrong',
|
|
|
|
|
+ 'failed_to_generate_key_string',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Save TransferKey document
|
|
// Save TransferKey document
|
|
|
let transferKeyString: string;
|
|
let transferKeyString: string;
|
|
|
try {
|
|
try {
|
|
|
- transferKeyString = await g2gTransferReceiverService.createTransferKey(appSiteUrlOrigin);
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ transferKeyString =
|
|
|
|
|
+ await g2gTransferReceiverService.createTransferKey(appSiteUrlOrigin);
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Error occurred while generating transfer key.', 'failed_to_generate_key'));
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Error occurred while generating transfer key.',
|
|
|
|
|
+ 'failed_to_generate_key',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return res.apiv3({ transferKey: transferKeyString });
|
|
return res.apiv3({ transferKey: transferKeyString });
|
|
|
- });
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @swagger
|
|
* @swagger
|
|
@@ -556,44 +693,65 @@ module.exports = (crowi: Crowi): Router => {
|
|
|
* type: string
|
|
* type: string
|
|
|
* description: The message of the result
|
|
* description: The message of the result
|
|
|
*/
|
|
*/
|
|
|
- pushRouter.post('/transfer',
|
|
|
|
|
|
|
+ pushRouter.post(
|
|
|
|
|
+ '/transfer',
|
|
|
accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
|
|
accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
|
|
|
- loginRequiredStrictly, adminRequired, validator.transfer, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
|
|
|
|
+ loginRequiredStrictly,
|
|
|
|
|
+ adminRequired,
|
|
|
|
|
+ validator.transfer,
|
|
|
|
|
+ apiV3FormValidator,
|
|
|
|
|
+ async (req: AuthorizedRequest, res: ApiV3Response) => {
|
|
|
const { transferKey, collections, optionsMap } = req.body;
|
|
const { transferKey, collections, optionsMap } = req.body;
|
|
|
|
|
|
|
|
// Parse transfer key
|
|
// Parse transfer key
|
|
|
let tk: TransferKey;
|
|
let tk: TransferKey;
|
|
|
try {
|
|
try {
|
|
|
tk = TransferKey.parse(transferKey);
|
|
tk = TransferKey.parse(transferKey);
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Transfer key is invalid', 'transfer_key_invalid'), 400);
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3('Transfer key is invalid', 'transfer_key_invalid'),
|
|
|
|
|
+ 400,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// get growi info
|
|
// get growi info
|
|
|
let destGROWIInfo: IDataGROWIInfo;
|
|
let destGROWIInfo: IDataGROWIInfo;
|
|
|
try {
|
|
try {
|
|
|
destGROWIInfo = await g2gTransferPusherService.askGROWIInfo(tk);
|
|
destGROWIInfo = await g2gTransferPusherService.askGROWIInfo(tk);
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
logger.error(err);
|
|
logger.error(err);
|
|
|
- return res.apiv3Err(new ErrorV3('Error occurred while asking GROWI info.', 'failed_to_ask_growi_info'));
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(
|
|
|
|
|
+ 'Error occurred while asking GROWI info.',
|
|
|
|
|
+ 'failed_to_ask_growi_info',
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Check if can transfer
|
|
// Check if can transfer
|
|
|
- const transferability = await g2gTransferPusherService.getTransferability(destGROWIInfo);
|
|
|
|
|
|
|
+ const transferability =
|
|
|
|
|
+ await g2gTransferPusherService.getTransferability(destGROWIInfo);
|
|
|
if (!transferability.canTransfer) {
|
|
if (!transferability.canTransfer) {
|
|
|
- return res.apiv3Err(new ErrorV3(transferability.reason, 'growi_incompatible_to_transfer'));
|
|
|
|
|
|
|
+ return res.apiv3Err(
|
|
|
|
|
+ new ErrorV3(transferability.reason, 'growi_incompatible_to_transfer'),
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Start transfer
|
|
// Start transfer
|
|
|
// DO NOT "await". Let it run in the background.
|
|
// DO NOT "await". Let it run in the background.
|
|
|
// Errors should be emitted through websocket.
|
|
// Errors should be emitted through websocket.
|
|
|
- g2gTransferPusherService.startTransfer(tk, req.user, collections, optionsMap, destGROWIInfo);
|
|
|
|
|
|
|
+ g2gTransferPusherService.startTransfer(
|
|
|
|
|
+ tk,
|
|
|
|
|
+ req.user,
|
|
|
|
|
+ collections,
|
|
|
|
|
+ optionsMap,
|
|
|
|
|
+ destGROWIInfo,
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
return res.apiv3({ message: 'Successfully requested auto transfer.' });
|
|
return res.apiv3({ message: 'Successfully requested auto transfer.' });
|
|
|
- });
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
// Merge receiveRouter and pushRouter
|
|
// Merge receiveRouter and pushRouter
|
|
|
router.use(receiveRouter, pushRouter);
|
|
router.use(receiveRouter, pushRouter);
|