Browse Source

create PageBulkExportJob on job start

Futa Arai 1 year ago
parent
commit
a2391a4e80

+ 1 - 5
apps/app/src/features/page-bulk-export/interfaces/page-bulk-export.ts

@@ -15,12 +15,8 @@ export interface IPageBulkExportJob {
   lastUploadedPagePath: string, // the path of page that was uploaded last
   uploadId: string, // upload ID of multipart upload of S3/GCS
   format: PageBulkExportFormat,
-  expireAt: Date, // the date at which job execution expires
-}
-
-export interface IPageBulkExportResult {
+  completedAt: Date | null, // the date at which job was completed
   attachment: Ref<IAttachment>,
-  expireAt: Date, // the date at which downloading of result expires
 }
 
 // snapshot of page info to upload

+ 6 - 4
apps/app/src/features/page-bulk-export/server/models/page-bulk-export-job.ts

@@ -2,7 +2,8 @@ import { type Document, type Model, Schema } from 'mongoose';
 
 import { getOrCreateModel } from '~/server/util/mongoose-utils';
 
-import { IPageBulkExportJob, PageBulkExportFormat } from '../../interfaces/page-bulk-export';
+import type { IPageBulkExportJob } from '../../interfaces/page-bulk-export';
+import { PageBulkExportFormat } from '../../interfaces/page-bulk-export';
 
 export interface PageBulkExportJobDocument extends IPageBulkExportJob, Document {}
 
@@ -11,10 +12,11 @@ export type PageBulkExportJobModel = Model<PageBulkExportJobDocument>
 const pageBulkExportJobSchema = new Schema<PageBulkExportJobDocument>({
   user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
   page: { type: Schema.Types.ObjectId, ref: 'Page', required: true },
-  lastUploadedPagePath: { type: String, required: true },
-  uploadId: { type: String, required: true },
+  lastUploadedPagePath: { type: String },
+  uploadId: { type: String, required: true, unique: true },
   format: { type: String, enum: Object.values(PageBulkExportFormat), required: true },
-  expireAt: { type: Date, required: true },
+  completedAt: { type: Date },
+  attachment: { type: Schema.Types.ObjectId, ref: 'Attachment' },
 }, { timestamps: true });
 
 export default getOrCreateModel<PageBulkExportJobDocument, PageBulkExportJobModel>('PageBulkExportJob', pageBulkExportJobSchema);

+ 0 - 16
apps/app/src/features/page-bulk-export/server/models/page-bulk-export-result.ts

@@ -1,16 +0,0 @@
-import { type Document, type Model, Schema } from 'mongoose';
-
-import { getOrCreateModel } from '~/server/util/mongoose-utils';
-
-import { IPageBulkExportResult } from '../../interfaces/page-bulk-export';
-
-export interface PageBulkExportResultDocument extends IPageBulkExportResult, Document {}
-
-export type PageBulkExportResultModel = Model<PageBulkExportResultDocument>
-
-const pageBulkExportResultSchema = new Schema<PageBulkExportResultDocument>({
-  attachment: { type: Schema.Types.ObjectId, ref: 'Attachment', required: true },
-  expireAt: { type: Date, required: true },
-}, { timestamps: true });
-
-export default getOrCreateModel<PageBulkExportResultDocument, PageBulkExportResultModel>('PageBulkExportResult', pageBulkExportResultSchema);

+ 5 - 4
apps/app/src/features/page-bulk-export/server/routes/apiv3/page-bulk-export.ts

@@ -1,9 +1,10 @@
 import { ErrorV3 } from '@growi/core/dist/models';
-import { Router, Request } from 'express';
+import type { Request } from 'express';
+import { Router } from 'express';
 import { body, validationResult } from 'express-validator';
 
-import Crowi from '~/server/crowi';
-import { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
+import type Crowi from '~/server/crowi';
+import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
 
 import { pageBulkExportService } from '../../service/page-bulk-export';
@@ -34,7 +35,7 @@ module.exports = (crowi: Crowi): Router => {
 
     const { path, format } = req.body;
 
-    pageBulkExportService?.bulkExportWithBasePagePath(path);
+    pageBulkExportService?.bulkExportWithBasePagePath(path, req.user);
     return res.apiv3({}, 204);
   });
 

+ 25 - 4
apps/app/src/features/page-bulk-export/server/service/page-bulk-export.ts

@@ -14,6 +14,9 @@ import type { IAwsMultipartUploader } from '~/server/service/file-uploader/aws/m
 import { getBufferToFixedSizeTransform } from '~/server/util/stream';
 import loggerFactory from '~/utils/logger';
 
+import { PageBulkExportFormat } from '../../interfaces/page-bulk-export';
+import type { PageBulkExportJobDocument } from '../models/page-bulk-export-job';
+import PageBulkExportJob from '../models/page-bulk-export-job';
 
 const logger = loggerFactory('growi:services:PageBulkExportService');
 
@@ -35,7 +38,15 @@ class PageBulkExportService {
     this.crowi = crowi;
   }
 
-  async bulkExportWithBasePagePath(basePagePath: string): Promise<void> {
+  async bulkExportWithBasePagePath(basePagePath: string, currentUser): Promise<void> {
+    const Page = mongoose.model<IPage, PageModel>('Page');
+    const basePage = await Page.findByPathAndViewer(basePagePath, currentUser, null, true);
+
+    if (basePage == null) {
+      this.handleExportError(new Error('Base page not found or not accessible'));
+      return;
+    }
+
     const timeStamp = (new Date()).getTime();
     const uploadKey = `page-bulk-export-${timeStamp}.zip`;
 
@@ -57,7 +68,15 @@ class PageBulkExportService {
       await this.handleExportError(err, multipartUploader);
       return;
     }
-    const multipartUploadWritable = this.getMultipartUploadWritable(multipartUploader);
+
+    const pageBulkExportJob = await PageBulkExportJob.create({
+      user: currentUser._id,
+      page: basePage._id,
+      uploadId: multipartUploader.uploadId,
+      format: PageBulkExportFormat.markdown,
+    });
+
+    const multipartUploadWritable = this.getMultipartUploadWritable(multipartUploader, pageBulkExportJob);
 
     // Cannot directly pipe from pagesWritable to zipArchiver due to how the 'append' method works.
     // Hence, execution of two pipelines is required.
@@ -65,7 +84,7 @@ class PageBulkExportService {
     pipeline(zipArchiver, bufferToPartSizeTransform, multipartUploadWritable, err => this.handleExportError(err, multipartUploader));
   }
 
-  async handleExportError(err: Error | null, multipartUploader: IAwsMultipartUploader | undefined): Promise<void> {
+  async handleExportError(err: Error | null, multipartUploader?: IAwsMultipartUploader): Promise<void> {
     if (err != null) {
       logger.error(err);
       if (multipartUploader != null) {
@@ -143,7 +162,7 @@ class PageBulkExportService {
     return zipArchiver;
   }
 
-  private getMultipartUploadWritable(multipartUploader: IAwsMultipartUploader): Writable {
+  private getMultipartUploadWritable(multipartUploader: IAwsMultipartUploader, pageBulkExportJob: PageBulkExportJobDocument): Writable {
     let partNumber = 1;
 
     return new Writable({
@@ -164,6 +183,8 @@ class PageBulkExportService {
       async final(callback) {
         try {
           await multipartUploader.completeUpload();
+          pageBulkExportJob.completedAt = new Date();
+          await pageBulkExportJob.save();
         }
         catch (err) {
           callback(err);

+ 7 - 2
apps/app/src/server/service/file-uploader/aws/multipart-upload.ts

@@ -19,6 +19,7 @@ export interface IAwsMultipartUploader {
   uploadPart(body: Buffer, partNumber: number): Promise<void>;
   completeUpload(): Promise<void>;
   abortUpload(): Promise<void>;
+  uploadId: string | undefined;
 }
 
 /**
@@ -33,7 +34,7 @@ export class AwsMultipartUploader implements IAwsMultipartUploader {
 
   private uploadKey: string;
 
-  private uploadId: string | undefined;
+  private _uploadId: string | undefined;
 
   private s3Client: S3Client;
 
@@ -47,6 +48,10 @@ export class AwsMultipartUploader implements IAwsMultipartUploader {
     this.uploadKey = uploadKey;
   }
 
+  get uploadId(): string | undefined {
+    return this._uploadId;
+  }
+
   async initUpload(): Promise<void> {
     this.validateUploadStatus(UploadStatus.BEFORE_INIT);
 
@@ -54,7 +59,7 @@ export class AwsMultipartUploader implements IAwsMultipartUploader {
       Bucket: this.bucket,
       Key: this.uploadKey,
     }));
-    this.uploadId = response.UploadId;
+    this._uploadId = response.UploadId;
     this.currentStatus = UploadStatus.IN_PROGRESS;
     logger.info(`Multipart upload initialized. Upload key: ${this.uploadKey}`);
   }