diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts index af75379d95..963ab73a62 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts @@ -11,6 +11,7 @@ import { promises as fs } from 'fs'; import path from 'path'; import { getApp } from '.'; import { FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, STORAGE_TYPE_LOCAL } from '../../constants'; +import PluginFileManagerServer from '../server'; const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '13000' } = process.env; @@ -50,6 +51,23 @@ describe('action', () => { describe('create / upload', () => { describe('default storage', () => { + it('createFileRecord', async () => { + const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer; + const model = await Plugin.createFileRecord({ + collection: 'attachments', + filePath: path.resolve(__dirname, './files/text.txt'), + }); + const matcher = { + title: 'text', + extname: '.txt', + path: '', + // size: 13, + meta: {}, + storageId: 1, + }; + expect(model.toJSON()).toMatchObject(matcher); + }); + it('upload file should be ok', async () => { const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, './files/text.txt'), diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts index 34eb2edf17..b757236179 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts @@ -11,15 +11,15 @@ import { Context, Next } from '@nocobase/actions'; import { koaMulter as multer } from '@nocobase/utils'; import Path from 'path'; +import Plugin from '..'; import { + FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, FILE_SIZE_LIMIT_MAX, - FILE_FIELD_NAME, - LIMIT_FILES, FILE_SIZE_LIMIT_MIN, + LIMIT_FILES, } from '../../constants'; import * as Rules from '../rules'; -import Plugin from '..'; // TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理 function getFileFilter(storage) { @@ -33,7 +33,7 @@ function getFileFilter(storage) { }; } -function getFileData(ctx: Context) { +export function getFileData(ctx: Context) { const { [FILE_FIELD_NAME]: file, storage } = ctx; if (!file) { return ctx.throw(400, 'file validation failed'); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts index 293d9bb33b..f5fd8cd13f 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts @@ -7,29 +7,77 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { resolve } from 'path'; - import { Plugin } from '@nocobase/server'; import { Registry } from '@nocobase/utils'; +import { basename, resolve } from 'path'; + +import { Transactionable } from '@nocobase/database'; +import fs from 'fs'; +import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; import { FileModel } from './FileModel'; import initActions from './actions'; +import { getFileData } from './actions/attachments'; +import { AttachmentInterface } from './interfaces/attachment-interface'; import { IStorage, StorageModel } from './storages'; -import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; -import StorageTypeLocal from './storages/local'; import StorageTypeAliOss from './storages/ali-oss'; +import StorageTypeLocal from './storages/local'; import StorageTypeS3 from './storages/s3'; import StorageTypeTxCos from './storages/tx-cos'; -import { AttachmentInterface } from './interfaces/attachment-interface'; export type * from './storages'; const DEFAULT_STORAGE_TYPE = STORAGE_TYPE_LOCAL; +export type FileRecordOptions = { + collection: string; + filePath: string; +} & Transactionable; + export default class PluginFileManagerServer extends Plugin { storageTypes = new Registry(); storagesCache = new Map(); + async createFileRecord(options: FileRecordOptions) { + const { collection, filePath, transaction } = options; + const storageRepository = this.db.getRepository('storages'); + const collectionRepository = this.db.getRepository(collection); + const storage = await storageRepository.findOne(); + + const fileStream = fs.createReadStream(filePath); + + if (!storage) { + throw new Error('[file-manager] no linked or default storage provided'); + } + + const storageConfig = this.storageTypes.get(storage.type); + + if (!storageConfig) { + throw new Error(`[file-manager] storage type "${storage.type}" is not defined`); + } + + const engine = storageConfig.make(storage); + + const file = { + originalname: basename(filePath), + path: filePath, + stream: fileStream, + } as any; + + await new Promise((resolve, reject) => { + engine._handleFile({} as any, file, (error, info) => { + if (error) { + reject(error); + } + Object.assign(file, info); + resolve(info); + }); + }); + + const values = getFileData({ app: this.app, file, storage, request: { body: {} } } as any); + return await collectionRepository.create({ values, transaction }); + } + async loadStorages(options?: { transaction: any }) { const repository = this.db.getRepository('storages'); const storages = await repository.find({