@@ -67,10 +69,78 @@ describe('UnixTimestamp', () => {
`);
});
+ it('millisecond', async () => {
+ await renderAppOptions({
+ Component: UnixTimestamp,
+ value: 1712819630000,
+ props: {
+ accuracy: 'millisecond',
+ },
+ });
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
+ });
+ });
+
+ it('second', async () => {
+ await renderAppOptions({
+ Component: UnixTimestamp,
+ value: 1712819630,
+ props: {
+ accuracy: 'second',
+ },
+ });
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
+ });
+ });
+
+ it('string', async () => {
+ await renderAppOptions({
+ Component: UnixTimestamp,
+ value: '2024-04-11',
+ props: {
+ accuracy: 'millisecond',
+ },
+ });
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
+ });
+ });
+
+ it('change', async () => {
+ const onChange = vitest.fn();
+ await renderAppOptions({
+ Component: UnixTimestamp,
+ value: '2024-04-11',
+ onChange,
+ props: {
+ accuracy: 'millisecond',
+ },
+ });
+ await userEvent.click(screen.getByRole('textbox'));
+
+ await waitFor(() => {
+ expect(screen.queryByRole('table')).toBeInTheDocument();
+ });
+
+ await userEvent.click(document.querySelector('td[title="2024-04-12"]'));
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox')).toHaveValue('2024-04-12');
+ });
+ expect(onChange).toBeCalledWith(1712880000000);
+ });
+
it('read pretty', async () => {
const { container } = await renderReadPrettyApp({
- Component: UnixTimestamp as any,
+ Component: UnixTimestamp,
value: '2024-04-11',
+ props: {
+ accuracy: 'millisecond',
+ },
});
expect(screen.getByText('2024-04-11')).toBeInTheDocument();
diff --git a/packages/core/database/src/__tests__/fields/date-only.test.ts b/packages/core/database/src/__tests__/fields/date-only.test.ts
deleted file mode 100644
index 1b6d9a60f7..0000000000
--- a/packages/core/database/src/__tests__/fields/date-only.test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * This file is part of the NocoBase (R) project.
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
- * Authors: NocoBase Team.
- *
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
- * For more information, please refer to: https://www.nocobase.com/agreement.
- */
-
-import { Database, mockDatabase } from '@nocobase/database';
-
-describe('date only', () => {
- let db: Database;
-
- beforeEach(async () => {
- db = mockDatabase({
- timezone: '+08:00',
- });
- await db.clean({ drop: true });
- });
-
- afterEach(async () => {
- await db.close();
- });
-
- it('should set date field with dateOnly', async () => {
- db.collection({
- name: 'tests',
- fields: [{ name: 'date1', type: 'dateOnly' }],
- });
-
- await db.sync();
-
- const item = await db.getRepository('tests').create({
- values: {
- date1: '2023-03-24',
- },
- });
-
- expect(item.get('date1')).toBe('2023-03-24');
- });
-});
diff --git a/packages/core/database/src/__tests__/fields/date.test.ts b/packages/core/database/src/__tests__/fields/date.test.ts
index 4c6b373721..e1a2ff189e 100644
--- a/packages/core/database/src/__tests__/fields/date.test.ts
+++ b/packages/core/database/src/__tests__/fields/date.test.ts
@@ -11,69 +11,6 @@ import { mockDatabase } from '../';
import { Database } from '../../database';
import { Repository } from '../../repository';
-describe('timezone', () => {
- let db: Database;
-
- beforeEach(async () => {
- db = mockDatabase({
- timezone: '+08:00',
- });
- await db.clean({ drop: true });
- });
-
- afterEach(async () => {
- await db.close();
- });
-
- describe('timezone', () => {
- test('custom', async () => {
- db.collection({
- name: 'tests',
- timestamps: false,
- fields: [{ name: 'date1', type: 'date', timezone: '+06:00' }],
- });
-
- await db.sync();
- const repository = db.getRepository('tests');
- const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } });
- const date1 = instance.get('date1');
- expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z');
- });
-
- test('client', async () => {
- db.collection({
- name: 'tests',
- timestamps: false,
- fields: [{ name: 'date1', type: 'date', timezone: 'client' }],
- });
-
- await db.sync();
- const repository = db.getRepository('tests');
- const instance = await repository.create({
- values: { date1: '2023-03-24 01:00:00' },
- context: {
- timezone: '+01:00',
- },
- });
- const date1 = instance.get('date1');
- expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z');
- });
-
- test('server', async () => {
- db.collection({
- name: 'tests',
- fields: [{ name: 'date1', type: 'date', timezone: 'server' }],
- });
-
- await db.sync();
- const repository = db.getRepository('tests');
- const instance = await repository.create({ values: { date1: '2023-03-24 08:00:00' } });
- const date1 = instance.get('date1');
- expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z');
- });
- });
-});
-
describe('date-field', () => {
let db: Database;
let repository: Repository;
@@ -93,80 +30,16 @@ describe('date-field', () => {
await db.close();
});
- it('should set default to current time', async () => {
- const c1 = db.collection({
- name: 'test11',
- fields: [
- {
- name: 'date1',
- type: 'date',
- defaultToCurrentTime: true,
- },
- ],
- });
-
- await db.sync();
-
- const instance = await c1.repository.create({});
- const date1 = instance.get('date1');
- expect(date1).toBeDefined();
- });
-
- it('should set to current time when update', async () => {
- const c1 = db.collection({
- name: 'test11',
- fields: [
- {
- name: 'date1',
- type: 'date',
- onUpdateToCurrentTime: true,
- },
- {
- name: 'title',
- type: 'string',
- },
- ],
- });
-
- await db.sync();
-
- const instance = await c1.repository.create({
+ const createExpectToBe = async (key, actual, expected) => {
+ const instance = await repository.create({
values: {
- title: 'test',
+ [key]: actual,
},
});
-
- const date1Val = instance.get('date1');
- expect(date1Val).toBeDefined();
-
- console.log('update');
- await c1.repository.update({
- values: {
- title: 'test2',
- },
- filter: {
- id: instance.get('id'),
- },
- });
-
- await instance.reload();
-
- const date1Val2 = instance.get('date1');
- expect(date1Val2).toBeDefined();
-
- expect(date1Val2.getTime()).toBeGreaterThan(date1Val.getTime());
- });
+ return expect(instance.get(key).toISOString()).toEqual(expected);
+ };
test('create', async () => {
- const createExpectToBe = async (key, actual, expected) => {
- const instance = await repository.create({
- values: {
- [key]: actual,
- },
- });
- return expect(instance.get(key).toISOString()).toEqual(expected);
- };
-
// sqlite 时区不能自定义,只有 +00:00,postgres 和 mysql 可以自定义 DB_TIMEZONE
await createExpectToBe('date1', '2023-03-24', '2023-03-24T00:00:00.000Z');
await createExpectToBe('date1', '2023-03-24T16:00:00.000Z', '2023-03-24T16:00:00.000Z');
diff --git a/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts b/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts
deleted file mode 100644
index d590653811..0000000000
--- a/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * This file is part of the NocoBase (R) project.
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
- * Authors: NocoBase Team.
- *
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
- * For more information, please refer to: https://www.nocobase.com/agreement.
- */
-
-import { Database, mockDatabase } from '@nocobase/database';
-import moment from 'moment';
-
-describe('unix timestamp field', () => {
- let db: Database;
-
- beforeEach(async () => {
- db = mockDatabase();
- await db.clean({ drop: true });
- });
-
- afterEach(async () => {
- await db.close();
- });
-
- it('should set default to current time', async () => {
- const c1 = db.collection({
- name: 'test11',
- fields: [
- {
- name: 'date1',
- type: 'unixTimestamp',
- defaultToCurrentTime: true,
- },
- ],
- });
-
- await db.sync();
-
- const instance = await c1.repository.create({});
- const date1 = instance.get('date1');
- expect(date1).toBeDefined();
-
- console.log(instance.toJSON());
- });
-
- it('should set date value', async () => {
- const c1 = db.collection({
- name: 'test12',
- fields: [
- {
- name: 'date1',
- type: 'unixTimestamp',
- },
- ],
- });
-
- await db.sync();
-
- await c1.repository.create({
- values: {
- date1: '2021-01-01T00:00:00Z',
- },
- });
-
- const item = await c1.repository.findOne();
- const val = item.get('date1');
- const date = moment(val).utc().format('YYYY-MM-DD HH:mm:ss');
- expect(date).toBe('2021-01-01 00:00:00');
- });
-
- describe('timezone', () => {
- test('custom', async () => {
- db.collection({
- name: 'tests',
- timestamps: false,
- fields: [{ name: 'date1', type: 'unixTimestamp', timezone: '+06:00' }],
- });
-
- await db.sync();
- const repository = db.getRepository('tests');
- const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } });
- const date1 = instance.get('date1');
- expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z');
- });
- });
-});
diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts
index 993f1be825..a3b813b96b 100644
--- a/packages/core/database/src/database.ts
+++ b/packages/core/database/src/database.ts
@@ -34,6 +34,7 @@ import {
import { SequelizeStorage, Umzug } from 'umzug';
import { Collection, CollectionOptions, RepositoryType } from './collection';
import { CollectionFactory } from './collection-factory';
+import { CollectionGroupManager } from './collection-group-manager';
import { ImporterReader, ImportFileExtension } from './collection-importer';
import DatabaseUtils from './database-utils';
import ReferencesMap from './features/references-map';
@@ -41,6 +42,7 @@ import { referentialIntegrityCheck } from './features/referential-integrity-chec
import { ArrayFieldRepository } from './field-repository/array-field-repository';
import * as FieldTypes from './fields';
import { Field, FieldContext, RelationField } from './fields';
+import { checkDatabaseVersion } from './helpers';
import { InheritedCollection } from './inherited-collection';
import InheritanceMap from './inherited-map';
import { InterfaceManager } from './interface-manager';
@@ -219,9 +221,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
}
}
- // @ts-ignore
- opts.rawTimezone = opts.timezone;
-
if (options.dialect === 'sqlite') {
delete opts.timezone;
} else if (!opts.timezone) {
@@ -852,8 +851,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
* @internal
*/
async checkVersion() {
- return true;
- // return await checkDatabaseVersion(this);
+ return await checkDatabaseVersion(this);
}
/**
diff --git a/packages/core/database/src/fields/date-field.ts b/packages/core/database/src/fields/date-field.ts
index 8cae2221d5..f40b27de3e 100644
--- a/packages/core/database/src/fields/date-field.ts
+++ b/packages/core/database/src/fields/date-field.ts
@@ -10,14 +10,8 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field } from './field';
-const datetimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
-
-function isValidDatetime(str) {
- return datetimeRegex.test(str);
-}
-
export class DateField extends Field {
- get dataType(): any {
+ get dataType() {
return DataTypes.DATE(3);
}
@@ -39,59 +33,6 @@ export class DateField extends Field {
return props.gmt;
}
- init() {
- const { name, defaultToCurrentTime, onUpdateToCurrentTime, timezone } = this.options;
-
- this.resolveTimeZone = (context) => {
- // @ts-ignore
- const serverTimeZone = this.database.options.rawTimezone;
- if (timezone === 'server') {
- return serverTimeZone;
- }
-
- if (timezone === 'client') {
- return context?.timezone || serverTimeZone;
- }
-
- if (timezone) {
- return timezone;
- }
-
- return serverTimeZone;
- };
-
- this.beforeSave = async (instance, options) => {
- const value = instance.get(name);
-
- if (!value && instance.isNewRecord && defaultToCurrentTime) {
- instance.set(name, new Date());
- return;
- }
-
- if (onUpdateToCurrentTime) {
- instance.set(name, new Date());
- return;
- }
- };
- }
-
- setter(value, options) {
- if (value === null) {
- return value;
- }
- if (value instanceof Date) {
- return value;
- }
-
- if (typeof value === 'string' && isValidDatetime(value)) {
- const dateTimezone = this.resolveTimeZone(options?.context);
- const dateString = `${value} ${dateTimezone}`;
- return new Date(dateString);
- }
-
- return value;
- }
-
bind() {
super.bind();
@@ -110,13 +51,6 @@ export class DateField extends Field {
// @ts-ignore
model.refreshAttributes();
}
-
- this.on('beforeSave', this.beforeSave);
- }
-
- unbind() {
- super.unbind();
- this.off('beforeSave', this.beforeSave);
}
}
diff --git a/packages/core/database/src/fields/date-only-field.ts b/packages/core/database/src/fields/date-only-field.ts
deleted file mode 100644
index 5fce9d1b74..0000000000
--- a/packages/core/database/src/fields/date-only-field.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * This file is part of the NocoBase (R) project.
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
- * Authors: NocoBase Team.
- *
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
- * For more information, please refer to: https://www.nocobase.com/agreement.
- */
-
-import { BaseColumnFieldOptions, Field } from './field';
-import { DataTypes } from 'sequelize';
-
-export class DateOnlyField extends Field {
- get dataType(): any {
- return DataTypes.DATEONLY;
- }
-}
-
-export interface DateOnlyFieldOptions extends BaseColumnFieldOptions {
- type: 'dateOnly';
-}
diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts
index e4b0b70024..b9f230dd4f 100644
--- a/packages/core/database/src/fields/field.ts
+++ b/packages/core/database/src/fields/field.ts
@@ -56,7 +56,7 @@ export abstract class Field {
return this.options.type;
}
- abstract get dataType(): any;
+ abstract get dataType();
isRelationField() {
return false;
@@ -171,13 +171,11 @@ export abstract class Field {
Object.assign(opts, { type: this.database.sequelize.normalizeDataType(this.dataType) });
}
- Object.assign(opts, this.additionalSequelizeOptions());
-
return opts;
}
- additionalSequelizeOptions() {
- return {};
+ isSqlite() {
+ return this.database.sequelize.getDialect() === 'sqlite';
}
typeToString() {
diff --git a/packages/core/database/src/fields/index.ts b/packages/core/database/src/fields/index.ts
index d698e44cd6..610b6f1ad1 100644
--- a/packages/core/database/src/fields/index.ts
+++ b/packages/core/database/src/fields/index.ts
@@ -36,8 +36,6 @@ import { UUIDFieldOptions } from './uuid-field';
import { VirtualFieldOptions } from './virtual-field';
import { NanoidFieldOptions } from './nanoid-field';
import { EncryptionField } from './encryption-field';
-import { UnixTimestampFieldOptions } from './unix-timestamp-field';
-import { DateOnlyFieldOptions } from './date-only-field';
export * from './array-field';
export * from './belongs-to-field';
@@ -45,7 +43,6 @@ export * from './belongs-to-many-field';
export * from './boolean-field';
export * from './context-field';
export * from './date-field';
-export * from './date-only-field';
export * from './field';
export * from './has-many-field';
export * from './has-one-field';
@@ -64,7 +61,6 @@ export * from './uuid-field';
export * from './virtual-field';
export * from './nanoid-field';
export * from './encryption-field';
-export * from './unix-timestamp-field';
export type FieldOptions =
| BaseFieldOptions
@@ -85,8 +81,6 @@ export type FieldOptions =
| SetFieldOptions
| TimeFieldOptions
| DateFieldOptions
- | DateOnlyFieldOptions
- | UnixTimestampFieldOptions
| UidFieldOptions
| UUIDFieldOptions
| NanoidFieldOptions
diff --git a/packages/core/database/src/fields/unix-timestamp-field.ts b/packages/core/database/src/fields/unix-timestamp-field.ts
deleted file mode 100644
index fc634ecde5..0000000000
--- a/packages/core/database/src/fields/unix-timestamp-field.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * This file is part of the NocoBase (R) project.
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
- * Authors: NocoBase Team.
- *
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
- * For more information, please refer to: https://www.nocobase.com/agreement.
- */
-
-import { DataTypes } from 'sequelize';
-import { DateField } from './date-field';
-import { BaseColumnFieldOptions } from './field';
-
-export class UnixTimestampField extends DateField {
- get dataType() {
- return DataTypes.BIGINT;
- }
-
- additionalSequelizeOptions(): {} {
- const { name } = this.options;
- let { accuracy } = this.options;
-
- if (this.options?.uiSchema['x-component-props']?.accuracy) {
- accuracy = this.options?.uiSchema['x-component-props']?.accuracy;
- }
-
- if (!accuracy) {
- accuracy = 'second';
- }
-
- let rationalNumber = 1000;
-
- if (accuracy === 'millisecond') {
- rationalNumber = 1;
- }
-
- return {
- get() {
- const value = this.getDataValue(name);
- if (value === null || value === undefined) {
- return value;
- }
-
- return new Date(value * rationalNumber);
- },
- set(value) {
- if (value === null || value === undefined) {
- this.setDataValue(name, value);
- } else {
- // date to unix timestamp
- this.setDataValue(name, Math.floor(new Date(value).getTime() / rationalNumber));
- }
- },
- };
- }
-}
-
-export interface UnixTimestampFieldOptions extends BaseColumnFieldOptions {
- type: 'unix-timestamp';
-}
diff --git a/packages/core/database/src/model.ts b/packages/core/database/src/model.ts
index 9ec745fcca..9152ae6b39 100644
--- a/packages/core/database/src/model.ts
+++ b/packages/core/database/src/model.ts
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import lodash from 'lodash';
+import lodash, { isPlainObject } from 'lodash';
import { Model as SequelizeModel, ModelStatic } from 'sequelize';
import { Collection } from './collection';
import { Database } from './database';
@@ -50,21 +50,6 @@ export class Model
(values, {
...options,
@@ -645,7 +645,7 @@ export class Repository exten
public requestLogger: Logger;
protected plugins = new Map();
protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance();
+ protected _started: Date | null = null;
private _authenticated = false;
private _maintaining = false;
private _maintainingCommandStatus: MaintainingCommandStatus;
private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null;
private _actionCommand: Command;
+
+ /**
+ * @internal
+ */
private sqlLogger: Logger;
+ protected _logger: SystemLogger;
constructor(public options: ApplicationOptions) {
super();
@@ -242,8 +248,6 @@ export class Application exten
}
}
- protected _started: Date | null = null;
-
/**
* @experimental
*/
@@ -251,8 +255,6 @@ export class Application exten
return this._started;
}
- protected _logger: SystemLogger;
-
get logger() {
return this._logger;
}
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..df6c12f697 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,72 @@ describe('action', () => {
describe('create / upload', () => {
describe('default storage', () => {
+ it('should be create file record', async () => {
+ const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer;
+ const model = await Plugin.createFileRecord({
+ collectionName: '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('should be local2 storage', async () => {
+ const storage = await StorageRepo.create({
+ values: {
+ name: 'local2',
+ type: STORAGE_TYPE_LOCAL,
+ baseUrl: DEFAULT_LOCAL_BASE_URL,
+ rules: {
+ size: 1024,
+ },
+ paranoid: true,
+ },
+ });
+ const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer;
+ const model = await Plugin.createFileRecord({
+ collectionName: 'attachments',
+ storageName: 'local2',
+ filePath: path.resolve(__dirname, './files/text.txt'),
+ });
+ const matcher = {
+ title: 'text',
+ extname: '.txt',
+ path: '',
+ // size: 13,
+ meta: {},
+ storageId: storage.id,
+ };
+ expect(model.toJSON()).toMatchObject(matcher);
+ });
+
+ it.only('should be custom values', async () => {
+ const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer;
+ const model = await Plugin.createFileRecord({
+ collectionName: 'attachments',
+ filePath: path.resolve(__dirname, './files/text.txt'),
+ values: {
+ size: 22,
+ },
+ });
+ const matcher = {
+ title: 'text',
+ extname: '.txt',
+ path: '',
+ size: 22,
+ 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 807e3a4de5..3a67235a56 100644
--- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts
+++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts
@@ -7,14 +7,17 @@
* 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 StorageTypeAliOss from './storages/ali-oss';
@@ -26,10 +29,78 @@ export type * from './storages';
const DEFAULT_STORAGE_TYPE = STORAGE_TYPE_LOCAL;
+export type FileRecordOptions = {
+ collectionName: string;
+ filePath: string;
+ storageName?: string;
+ values?: any;
+} & Transactionable;
+
export default class PluginFileManagerServer extends Plugin {
storageTypes = new Registry();
storagesCache = new Map();
+ async createFileRecord(options: FileRecordOptions) {
+ const { values, storageName, collectionName, filePath, transaction } = options;
+ const collection = this.db.getCollection(collectionName);
+ if (!collection) {
+ throw new Error(`collection does not exist`);
+ }
+ const storageRepository = this.db.getRepository('storages');
+ const collectionRepository = this.db.getRepository(collectionName);
+ const name = storageName || collection.options.storage;
+
+ let storageInstance;
+ if (name) {
+ storageInstance = await storageRepository.findOne({
+ filter: {
+ name,
+ },
+ });
+ }
+
+ if (!storageInstance) {
+ storageInstance = await storageRepository.findOne({
+ filter: {
+ default: true,
+ },
+ });
+ }
+
+ const fileStream = fs.createReadStream(filePath);
+
+ if (!storageInstance) {
+ throw new Error('[file-manager] no linked or default storage provided');
+ }
+
+ const storageConfig = this.storageTypes.get(storageInstance.type);
+
+ if (!storageConfig) {
+ throw new Error(`[file-manager] storage type "${storageInstance.type}" is not defined`);
+ }
+
+ const engine = storageConfig.make(storageInstance);
+
+ 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 data = getFileData({ app: this.app, file, storage: storageInstance, request: { body: {} } } as any);
+ return await collectionRepository.create({ values: { ...data, ...values }, transaction });
+ }
+
async loadStorages(options?: { transaction: any }) {
const repository = this.db.getRepository('storages');
const storages = await repository.find({
diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx
index c36b079151..7725b03359 100644
--- a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx
+++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx
@@ -7,14 +7,14 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import React, { FC, useState } from 'react';
-import { QRCode, Popover, Button } from 'antd';
import { QrcodeOutlined } from '@ant-design/icons';
+import { Button, Popover, QRCode } from 'antd';
+import React, { FC, useState } from 'react';
+import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
+import { css, DesignableSwitch, Icon, useApp } from '@nocobase/client';
import { usePluginTranslation } from '../locale';
import { useSize } from './sizeContext';
-import { css, DesignableSwitch, Icon } from '@nocobase/client';
-import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
const PadSvg = () => (