Taichi Masuyama 3 tahun lalu
induk
melakukan
db9ea2ac65

+ 9 - 0
packages/app/src/server/crowi/index.js

@@ -22,6 +22,7 @@ import AclService from '../service/acl';
 import AppService from '../service/app';
 import AttachmentService from '../service/attachment';
 import ConfigManager from '../service/config-manager';
+import { G2GTransferService } from '../service/g2g-transfer';
 import { InstallerService } from '../service/installer';
 import PageService from '../service/page';
 import PageGrantService from '../service/page-grant';
@@ -53,6 +54,7 @@ function Crowi() {
   this.config = {};
   this.configManager = null;
   this.s2sMessagingService = null;
+  this.g2gTransferService = null;
   this.mailService = null;
   this.passportService = null;
   this.globalNotificationService = null;
@@ -121,6 +123,7 @@ Crowi.prototype.init = async function() {
     this.setupSearcher(),
     this.setupMailer(),
     this.setupSlackIntegrationService(),
+    this.setupG2GTransferService(),
     this.setUpFileUpload(),
     this.setUpFileUploaderSwitchService(),
     this.setupAttachmentService(),
@@ -741,4 +744,10 @@ Crowi.prototype.setupSlackIntegrationService = async function() {
   }
 };
 
+Crowi.prototype.setupG2GTransferService = async function() {
+  if (this.g2gTransferService == null) {
+    this.g2gTransferService = new G2GTransferService(this);
+  }
+};
+
 export default Crowi;

+ 1 - 1
packages/app/src/server/models/transfer-key.ts

@@ -1,9 +1,9 @@
-import { getOrCreateModel } from '@growi/core';
 import { Schema } from 'mongoose';
 
 import { ITransferKey } from '~/interfaces/transfer-key';
 
 import loggerFactory from '../../utils/logger';
+import { getOrCreateModel } from '../util/mongoose-utils';
 
 const logger = loggerFactory('growi:models:transfer-key');
 

+ 32 - 11
packages/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -1,9 +1,7 @@
 import express, { NextFunction, Request, Router } from 'express';
-import { Types as MongooseTypes } from 'mongoose';
+import { body } from 'express-validator';
 
-import TransferKeyModel from '~/server/models/transfer-key';
 import loggerFactory from '~/utils/logger';
-import { TransferKey } from '~/utils/vo/transfer-key';
 
 import Crowi from '../../crowi';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
@@ -13,15 +11,23 @@ import { ApiV3Response } from './interfaces/apiv3-response';
 
 const logger = loggerFactory('growi:routes:apiv3:transfer');
 
+const validator = {
+  transfer: [
+    body('transferKey').isString().withMessage('transferKey is required'),
+  ],
+};
+
 /*
  * Routes
  */
-export default (crowi: Crowi): Router => {
+module.exports = (crowi: Crowi): Router => {
   const isInstalled = crowi.configManager?.getConfig('crowi', 'app:installed');
 
   const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 
+  // Middleware
   const adminRequiredIfInstalled = (req: Request, res: ApiV3Response, next: NextFunction) => {
     if (!isInstalled) {
       next();
@@ -31,6 +37,7 @@ export default (crowi: Crowi): Router => {
     return adminRequired(req, res, next);
   };
 
+  // Middleware
   const appSiteUrlRequiredIfNotInstalled = (req: Request, res: ApiV3Response, next: NextFunction) => {
     if (!isInstalled && req.body.appSiteUrl != null) {
       next();
@@ -45,18 +52,26 @@ export default (crowi: Crowi): Router => {
     return res.apiv3Err(new ErrorV3('Body param "appSiteUrl" is required when GROWI is NOT installed yet'), 400);
   };
 
+  // Middleware to check if key is valid or not
+  const validateTransferKey = async(req: Request, res: ApiV3Response, next: NextFunction) => {
+    const { transferKey } = req.body;
+    // TODO: Check key
+    next();
+  };
+
   const router = express.Router();
 
-  // TODO: Refactor process as TransferService methods
-  router.post('/generate-key', accessTokenParser, adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
-    const appSiteUrl = req.body.appSiteUrl ?? crowi.configManager?.getConfig('crowi', 'app:siteUrl');
+  router.post('/', validator.transfer, apiV3FormValidator, validateTransferKey, async(req: Request, res: ApiV3Response) => {
+    return;
+  });
 
-    const uuid = new MongooseTypes.ObjectId().toString();
+  router.post('/generate-key', /* accessTokenParser, adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, */ async(req: Request, res: ApiV3Response) => {
+    const strAppSiteUrl = req.body.appSiteUrl ?? crowi.configManager?.getConfig('crowi', 'app:siteUrl');
 
     // Generate transfer key string
-    let transferKeyString: string;
+    let appSiteUrl: URL;
     try {
-      transferKeyString = TransferKey.generateKeyString(new URL(appSiteUrl), uuid);
+      appSiteUrl = new URL(strAppSiteUrl);
     }
     catch (err) {
       logger.error(err);
@@ -64,8 +79,9 @@ export default (crowi: Crowi): Router => {
     }
 
     // Save TransferKey document
+    let transferKeyString: string;
     try {
-      await TransferKeyModel.create({ _id: uuid, appSiteUrl, value: transferKeyString });
+      transferKeyString = await crowi.g2gTransferService.createTransferKey(appSiteUrl);
     }
     catch (err) {
       logger.error(err);
@@ -75,5 +91,10 @@ export default (crowi: Crowi): Router => {
     return res.apiv3({ transferKey: transferKeyString });
   });
 
+  // eslint-disable-next-line max-len
+  router.post('/transfer', accessTokenParser, loginRequiredStrictly, adminRequired, validator.transfer, apiV3FormValidator, async(req: Request, res: ApiV3Response) => {
+    return;
+  });
+
   return router;
 };

+ 88 - 0
packages/app/src/server/service/g2g-transfer.ts

@@ -0,0 +1,88 @@
+import { Readable } from 'stream';
+
+import { Types as MongooseTypes } from 'mongoose';
+
+import TransferKeyModel from '~/server/models/transfer-key';
+import loggerFactory from '~/utils/logger';
+import { TransferKey } from '~/utils/vo/transfer-key';
+
+const logger = loggerFactory('growi:service:g2g-transfer');
+
+/**
+ * Data used for comparing to/from GROWI information
+ */
+export type IDataFromGROWIInfo = {
+  version: string
+  userLimit: number
+}
+
+interface Pusher {
+  /**
+   * Start transfer data between GROWIs
+   * @param {string} key Transfer key
+   */
+  startTransfer(key: string): Promise<Readable>
+}
+
+interface Receiver {
+  /**
+   * Check if key is not expired
+   * @param {string} key Transfer key
+   */
+  validateTransferKey(key: string): Promise<boolean>
+  /**
+   * Check if transfering is proceedable
+   * @param {IDataFromGROWIInfo} fromGROWIInfo
+   */
+  canTransfer(fromGROWIInfo: IDataFromGROWIInfo): Promise<boolean>
+}
+
+export class G2GTransferService implements Pusher, Receiver {
+
+  crowi: any;
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  constructor(crowi: any) {
+    this.crowi = crowi;
+  }
+
+  public async canTransfer(fromGROWIInfo: IDataFromGROWIInfo): Promise<boolean> { return true }
+
+  public async startTransfer(key: string): Promise<Readable> { return new Readable() }
+
+  public async validateTransferKey(key: string): Promise<boolean> { return true }
+
+  /**
+   * This method receives appSiteUrl to create a TransferKey document and returns generated transfer key string.
+   * UUID is the same value as the created document's _id.
+   * @param {URL} appSiteUrl URL type appSiteUrl
+   * @returns {string} Transfer key string (e.g. http://localhost:3000__grw_internal_tranferkey__<uuid>)
+   */
+  public async createTransferKey(appSiteUrl: URL): Promise<string> {
+    const uuid = new MongooseTypes.ObjectId().toString();
+
+    // Generate transfer key string
+    let transferKeyString: string;
+    try {
+      transferKeyString = TransferKey.generateKeyString(appSiteUrl, uuid);
+    }
+    catch (err) {
+      logger.error(err);
+      throw err;
+    }
+
+    // Save TransferKey document
+    try {
+      await TransferKeyModel.create({ _id: uuid, appSiteUrl, value: transferKeyString });
+    }
+    catch (err) {
+      logger.error(err);
+      throw err;
+    }
+
+    return transferKeyString;
+  }
+
+  private onCompleteTransfer(): void { return }
+
+}

+ 3 - 3
packages/app/src/utils/vo/transfer-key.ts

@@ -3,11 +3,11 @@
  */
 export class TransferKey {
 
-  private static _internalSeperator = '__grw_internal_tranferkey__'
+  private static _internalSeperator = '__grw_internal_tranferkey__';
 
-  public appUrl: URL
+  public appUrl: URL;
 
-  public key: string
+  public key: string;
 
   constructor(appUrl: URL, key: string) {
     this.appUrl = appUrl;