mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-21 13:52:17 +08:00
Merge branch 'main' into next
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 { Application } from '@nocobase/client-v2';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { AttachmentFieldInterface } from '../interfaces/attachment';
|
||||
import PluginFileManagerClientV2 from '../plugin';
|
||||
|
||||
describe('AttachmentFieldInterface', () => {
|
||||
it('provides v2 data source manager metadata for attachment fields', async () => {
|
||||
const app = new Application({
|
||||
plugins: [PluginFileManagerClientV2],
|
||||
});
|
||||
|
||||
await app.load();
|
||||
|
||||
const fieldInterface =
|
||||
app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface<AttachmentFieldInterface>('attachment');
|
||||
|
||||
expect(fieldInterface).toBeInstanceOf(AttachmentFieldInterface);
|
||||
expect(fieldInterface.group).toBe('media');
|
||||
expect(fieldInterface.default).toMatchObject({
|
||||
interface: 'attachment',
|
||||
type: 'belongsToMany',
|
||||
target: 'attachments',
|
||||
uiSchema: {
|
||||
type: 'array',
|
||||
'x-component': 'Upload.Attachment',
|
||||
},
|
||||
});
|
||||
expect(fieldInterface.configure?.items?.map((item) => item.name)).toEqual([
|
||||
'target',
|
||||
'targetKey',
|
||||
'uiSchema.x-component-props.accept',
|
||||
'uiSchema.x-component-props.multiple',
|
||||
]);
|
||||
});
|
||||
|
||||
it('initializes required belongsToMany keys', () => {
|
||||
const fieldInterface = new AttachmentFieldInterface({} as never);
|
||||
const values: Record<string, unknown> = {};
|
||||
|
||||
fieldInterface.initialize(values);
|
||||
|
||||
expect(values).toMatchObject({
|
||||
sourceKey: 'id',
|
||||
targetKey: 'id',
|
||||
});
|
||||
expect(values.through).toMatch(/^t_/);
|
||||
expect(values.foreignKey).toMatch(/^f_/);
|
||||
expect(values.otherKey).toMatch(/^f_/);
|
||||
});
|
||||
});
|
||||
@@ -36,6 +36,7 @@ export {
|
||||
export type { DefaultFieldProps } from './components/DefaultField';
|
||||
export type { PathFieldProps } from './components/PathField';
|
||||
export { CardUpload, UploadFieldModel } from './models/UploadFieldModel';
|
||||
export { AttachmentFieldInterface } from './interfaces/attachment';
|
||||
|
||||
// Preview registry consumed by file-previewer plugins (e.g. plugin-file-previewer-office)
|
||||
// to add custom preview handlers under v2 without going through the v1 `@nocobase/plugin-file-manager/client` entry.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 { uid } from '@formily/shared';
|
||||
import { CollectionFieldInterface } from '@nocobase/client-v2';
|
||||
import { tExpr } from '../locale';
|
||||
|
||||
export class AttachmentFieldInterface extends CollectionFieldInterface {
|
||||
name = 'attachment';
|
||||
type = 'object';
|
||||
group = 'media';
|
||||
title = tExpr('Attachment');
|
||||
isAssociation = true;
|
||||
default = {
|
||||
interface: 'attachment',
|
||||
type: 'belongsToMany',
|
||||
target: 'attachments',
|
||||
uiSchema: {
|
||||
type: 'array',
|
||||
'x-component': 'Upload.Attachment',
|
||||
'x-use-component-props': 'useAttachmentFieldProps',
|
||||
},
|
||||
};
|
||||
availableTypes = ['belongsToMany'];
|
||||
configure = {
|
||||
items: [
|
||||
{
|
||||
name: 'target',
|
||||
title: tExpr('File collection'),
|
||||
component: 'Select' as const,
|
||||
required: true,
|
||||
defaultValue: 'attachments',
|
||||
schema: {
|
||||
enum: '{{fileCollections}}',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'targetKey',
|
||||
defaultValue: 'id',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: 'uiSchema.x-component-props.accept',
|
||||
title: tExpr('MIME type'),
|
||||
component: 'Input' as const,
|
||||
componentProps: {
|
||||
placeholder: 'image/*',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'uiSchema.x-component-props.multiple',
|
||||
title: tExpr('Allow uploading multiple files'),
|
||||
component: 'Checkbox' as const,
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
filterable = {
|
||||
nested: true,
|
||||
children: [],
|
||||
};
|
||||
|
||||
initialize(values: Record<string, unknown>) {
|
||||
if (!values.through) {
|
||||
values.through = `t_${uid()}`;
|
||||
}
|
||||
if (!values.foreignKey) {
|
||||
values.foreignKey = `f_${uid()}`;
|
||||
}
|
||||
if (!values.otherKey) {
|
||||
values.otherKey = `f_${uid()}`;
|
||||
}
|
||||
if (!values.sourceKey) {
|
||||
values.sourceKey = 'id';
|
||||
}
|
||||
if (!values.targetKey) {
|
||||
values.targetKey = 'id';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
STORAGE_TYPE_TX_COS,
|
||||
} from '../constants';
|
||||
import { NAMESPACE } from '../common/constants';
|
||||
import { AttachmentFieldInterface } from './interfaces/attachment';
|
||||
import { tExpr } from './locale';
|
||||
|
||||
type UploadFileResult = {
|
||||
@@ -88,6 +89,8 @@ export class PluginFileManagerClientV2 extends Plugin<Record<string, never>, App
|
||||
storageTypes = new Map<string, StorageType>();
|
||||
|
||||
async load() {
|
||||
this.app.addFieldInterfaces([AttachmentFieldInterface]);
|
||||
|
||||
const title = this.app.i18n.t('File manager', { ns: NAMESPACE });
|
||||
const dataSourceManager = (this.app.pm.get('@nocobase/plugin-data-source-manager') ||
|
||||
this.app.pm.get('data-source-manager')) as
|
||||
|
||||
@@ -10,10 +10,23 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { PluginPublicFormsServer } from '../plugin';
|
||||
|
||||
type PublicFormsServerApp = ConstructorParameters<typeof PluginPublicFormsServer>[0];
|
||||
|
||||
function createPlugin() {
|
||||
return Object.create(PluginPublicFormsServer.prototype) as PluginPublicFormsServer & Record<string, any>;
|
||||
}
|
||||
|
||||
function createAclPlugin() {
|
||||
return new PluginPublicFormsServer(
|
||||
{
|
||||
db: {
|
||||
getCollection: vi.fn(() => null),
|
||||
},
|
||||
} as unknown as PublicFormsServerApp,
|
||||
{ name: 'public-forms' },
|
||||
);
|
||||
}
|
||||
|
||||
function setupGetMetaPlugin(options: { password?: string; enabled?: boolean } = {}) {
|
||||
const plugin = createPlugin();
|
||||
const visibleFlowModel = {
|
||||
@@ -77,6 +90,23 @@ function setupGetMetaPlugin(options: { password?: string; enabled?: boolean } =
|
||||
return { plugin, sign };
|
||||
}
|
||||
|
||||
type PublicFormAclContext = {
|
||||
PublicForm: {
|
||||
collectionName: string;
|
||||
targetCollections: string[];
|
||||
};
|
||||
action: {
|
||||
resourceName: string;
|
||||
actionName: string;
|
||||
params: {
|
||||
fileCollectionName?: string;
|
||||
};
|
||||
};
|
||||
permission?: {
|
||||
skip: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
describe('PluginPublicFormsServer', () => {
|
||||
it('keeps primary collection options in public form meta', async () => {
|
||||
const plugin = createPlugin();
|
||||
@@ -379,4 +409,27 @@ describe('PluginPublicFormsServer', () => {
|
||||
expect(plugin.isFlowModelDescendant).toHaveBeenCalledWith('pf1', 'popup-grid');
|
||||
expect(findModelById).toHaveBeenCalledWith('popup-grid', { includeAsyncNode: true });
|
||||
});
|
||||
|
||||
it('allows public form storage checks', async () => {
|
||||
const plugin = createAclPlugin();
|
||||
const ctx: PublicFormAclContext = {
|
||||
PublicForm: {
|
||||
collectionName: 'orders',
|
||||
targetCollections: [],
|
||||
},
|
||||
action: {
|
||||
resourceName: 'storages',
|
||||
actionName: 'check',
|
||||
params: {
|
||||
fileCollectionName: 'publicFiles',
|
||||
},
|
||||
},
|
||||
};
|
||||
const next = vi.fn(async () => undefined);
|
||||
|
||||
await plugin.parseACL(ctx, next);
|
||||
|
||||
expect(ctx.permission).toEqual({ skip: true });
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -347,7 +347,7 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
} else if (
|
||||
(['list', 'get'].includes(actionName) && ctx.PublicForm['targetCollections'].includes(resourceName)) ||
|
||||
(collection?.options.template === 'file' && actionName === 'create') ||
|
||||
(resourceName === 'storages' && ['getBasicInfo', 'createPresignedUrl'].includes(actionName)) ||
|
||||
(resourceName === 'storages' && ['getBasicInfo', 'createPresignedUrl', 'check'].includes(actionName)) ||
|
||||
(resourceName === 'vditor' && ['check'].includes(actionName)) ||
|
||||
(resourceName === 'map-configuration' && actionName === 'get')
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user