From 1fad6985dc8dcc68d639ef2c20b8dce6026e2489 Mon Sep 17 00:00:00 2001 From: Surmon Date: Thu, 29 Aug 2024 06:05:55 +0800 Subject: [PATCH] feat: v4.11.0 --- package.json | 2 +- src/modules/extension/extension.controller.ts | 31 +++++++++- .../extension/extension.service.dbbackup.ts | 10 +++- src/processors/helper/helper.service.aws.ts | 57 +++++++++++++++---- 4 files changed, 82 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 32b57b12..9be5b0c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nodepress", - "version": "4.10.0", + "version": "4.11.0", "description": "RESTful API service for Surmon.me blog", "author": "Surmon", "license": "MIT", diff --git a/src/modules/extension/extension.controller.ts b/src/modules/extension/extension.controller.ts index 240cdd8b..3d2bb78b 100755 --- a/src/modules/extension/extension.controller.ts +++ b/src/modules/extension/extension.controller.ts @@ -10,8 +10,8 @@ import { AdminOnlyGuard } from '@app/guards/admin-only.guard' import { AdminMaybeGuard } from '@app/guards/admin-maybe.guard' import { Responser } from '@app/decorators/responser.decorator' import { QueryParams, QueryParamsResult } from '@app/decorators/queryparams.decorator' -import { AWSService } from '@app/processors/helper/helper.service.aws' import { GoogleService } from '@app/processors/helper/helper.service.google' +import { AWSService } from '@app/processors/helper/helper.service.aws' import { StatisticService, Statistic } from './extension.service.statistic' import { DBBackupService } from './extension.service.dbbackup' import * as APP_CONFIG from '@app/app.config' @@ -39,11 +39,36 @@ export class ExtensionController { return this.dbBackupService.backup() } - @Post('upload') + @Get('static/list') + @UseGuards(AdminOnlyGuard) + @Responser.handle('Get file list from cloud storage') + async getStaticFileList(@QueryParams() { query }: QueryParamsResult) { + const minLimit = 80 + const numberLimit = Number(query.limit) + const limit = Number.isInteger(numberLimit) ? numberLimit : minLimit + const result = await this.awsService.getFileList({ + limit: limit < minLimit ? minLimit : limit, + prefix: query.prefix, + marker: query.marker, + region: APP_CONFIG.AWS.s3StaticRegion, + bucket: APP_CONFIG.AWS.s3StaticBucket + }) + + return { + ...result, + files: result.files.map((file) => ({ + ...file, + url: `${APP_CONFIG.APP.STATIC_URL}/${file.key}`, + lastModified: file.lastModified?.getTime() + })) + } + } + + @Post('static/upload') @UseGuards(AdminOnlyGuard) @UseInterceptors(FileInterceptor('file')) @Responser.handle('Upload file to cloud storage') - async uploadStatic(@UploadedFile() file: Express.Multer.File, @Body() body) { + async uploadStaticFile(@UploadedFile() file: Express.Multer.File, @Body() body) { const result = await this.awsService.uploadFile({ name: body.name, file: file.buffer, diff --git a/src/modules/extension/extension.service.dbbackup.ts b/src/modules/extension/extension.service.dbbackup.ts index 0ea4b023..2d86d453 100755 --- a/src/modules/extension/extension.service.dbbackup.ts +++ b/src/modules/extension/extension.service.dbbackup.ts @@ -12,7 +12,7 @@ import schedule from 'node-schedule' import { Injectable } from '@nestjs/common' import { EmailService } from '@app/processors/helper/helper.service.email' import { - UploadResult, + S3FileObject, AWSService, AWSStorageClass, AWSServerSideEncryption @@ -45,7 +45,11 @@ export class DBBackupService { public async backup() { try { const result = await this.doBackup() - const json = { ...result, size: (result.size / 1024).toFixed(2) + 'kb' } + const json = { + ...result, + lastModified: result.lastModified?.toLocaleString('zh'), + size: (result.size / 1024).toFixed(2) + 'kb' + } this.mailToAdmin('Database backup succeeded', JSON.stringify(json, null, 2), true) return result } catch (error) { @@ -64,7 +68,7 @@ export class DBBackupService { } private doBackup() { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!shell.which('mongodump')) { return reject('DB Backup script requires [mongodump]') } diff --git a/src/processors/helper/helper.service.aws.ts b/src/processors/helper/helper.service.aws.ts index 31527ca5..33dcc867 100755 --- a/src/processors/helper/helper.service.aws.ts +++ b/src/processors/helper/helper.service.aws.ts @@ -7,6 +7,7 @@ import { S3Client, PutObjectCommand, + ListObjectsCommand, GetObjectAttributesCommand, ObjectAttributes, StorageClass, @@ -28,11 +29,13 @@ export interface FileUploader { encryption?: ServerSideEncryption } -export interface UploadResult { +export interface S3FileObject { key: string url: string eTag: string size: number + lastModified?: Date + storageClass?: StorageClass } @Injectable() @@ -47,6 +50,11 @@ export class AWSService { }) } + private getAwsGeneralFileUrl(region: string, bucket: string, key: string) { + // https://stackoverflow.com/questions/44400227/how-to-get-the-url-of-a-file-on-aws-s3-using-aws-sdk + return `https://${bucket}.s3.${region}.amazonaws.com/${key}` + } + public getObjectAttributes(payload: { region: string; bucket: string; key: string }) { const s3Client = this.createClient(payload.region) const command = new GetObjectAttributesCommand({ @@ -57,7 +65,7 @@ export class AWSService { return s3Client.send(command) } - public uploadFile(payload: FileUploader): Promise { + public uploadFile(payload: FileUploader): Promise { const { region, bucket, name: key } = payload const s3Client = this.createClient(region) const command = new PutObjectCommand({ @@ -69,15 +77,42 @@ export class AWSService { ServerSideEncryption: payload.encryption }) return s3Client.send(command).then(() => { - return this.getObjectAttributes({ region, bucket, key }).then((attributes) => { - return { - key, - // https://stackoverflow.com/questions/44400227/how-to-get-the-url-of-a-file-on-aws-s3-using-aws-sdk - url: `https://${bucket}.s3.${region}.amazonaws.com/${key}`, - eTag: attributes.ETag!, - size: attributes.ObjectSize! - } - }) + return this.getObjectAttributes({ region, bucket, key }).then((attributes) => ({ + key, + url: this.getAwsGeneralFileUrl(region, bucket, key), + size: attributes.ObjectSize!, + eTag: attributes.ETag!, + lastModified: attributes.LastModified, + storageClass: attributes.StorageClass + })) }) } + + public getFileList(payload: { region: string; bucket: string; limit: number; prefix?: string; marker?: string }) { + const s3Client = this.createClient(payload.region) + const command = new ListObjectsCommand({ + Bucket: payload.bucket, + Marker: payload.marker, + Prefix: payload.prefix, + MaxKeys: payload.limit + }) + + return s3Client.send(command).then((result) => ({ + name: result.Name, + limit: result.MaxKeys, + prefix: result.Prefix, + marker: result.Marker, + files: (result.Contents ?? []).map((object) => ({ + key: object.Key!, + url: this.getAwsGeneralFileUrl(payload.region, payload.bucket, object.Key!), + eTag: object.ETag!, + size: object.Size!, + lastModified: object.LastModified, + storageClass: object.StorageClass + })) + })) + } + + // TODO + public async deleteFile() {} }