Katherine 2fcd584846
feat: support attachment file fields in public forms (#5749)
* feat: support attachment file fields in public forms

* fix: bug
2024-12-02 12:38:23 +08:00

216 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
import { Plugin } from '@nocobase/server';
import { parseAssociationNames } from './hook';
class PasswordError extends Error {}
export class PluginPublicFormsServer extends Plugin {
async parseCollectionData(formCollection, appends) {
const collection = this.db.getCollection(formCollection);
const collections = [
{
name: collection.name,
fields: collection.getFields().map((v) => {
return {
...v.options,
};
}),
template: collection.options.template,
},
];
return collections.concat(
appends.map((v) => {
const targetCollection = this.db.getCollection(v);
return {
name: targetCollection.name,
fields: targetCollection.getFields().map((v) => {
return {
...v.options,
};
}),
template: targetCollection.options.template,
};
}),
);
}
async getMetaByTk(filterByTk: string, options: { password?: string; token?: string }) {
const { token, password } = options;
const publicForms = this.db.getRepository('publicForms');
const uiSchema = this.db.getRepository<UiSchemaRepository>('uiSchemas');
const instance = await publicForms.findOne({
filter: {
key: filterByTk,
},
});
if (!instance) {
throw new Error('The form is not found');
}
if (!instance.get('enabled')) {
return null;
}
if (!token) {
if (instance.get('password')) {
if (password === undefined) {
return {
passwordRequired: true,
};
}
if (instance.get('password') !== password) {
throw new PasswordError('Please enter your password');
}
}
}
const keys = instance.collection.split(':');
const collectionName = keys.pop();
const dataSourceKey = keys.pop() || 'main';
const schema = await uiSchema.getJsonSchema(filterByTk);
const { getAssociationAppends } = parseAssociationNames(dataSourceKey, collectionName, this.app, schema);
const { appends } = getAssociationAppends();
const collections = await this.parseCollectionData(collectionName, appends);
return {
dataSource: {
key: dataSourceKey,
displayName: dataSourceKey,
collections,
},
token: this.app.authManager.jwt.sign(
{
collectionName,
formKey: filterByTk,
targetCollections: appends,
},
{
expiresIn: '1h',
},
),
schema,
};
}
// TODO
getPublicFormsMeta = async (ctx, next) => {
const token = ctx.get('X-Form-Token');
const { filterByTk, password } = ctx.action.params;
try {
ctx.body = await this.getMetaByTk(filterByTk, { password, token });
} catch (error) {
if (error instanceof PasswordError) {
ctx.throw(401, error.message);
} else {
throw error;
}
}
await next();
};
parseToken = async (ctx, next) => {
if (!ctx.action) {
return next();
}
const { actionName, resourceName, params } = ctx.action;
// 有密码时,跳过 token
if (resourceName === 'publicForms' && actionName === 'getMeta' && params.password) {
return next();
}
const jwt = this.app.authManager.jwt;
const token = ctx.get('X-Form-Token');
if (token) {
try {
const tokenData = await jwt.decode(token);
ctx.PublicForm = {
collectionName: tokenData.collectionName,
formKey: tokenData.formKey,
targetCollections: tokenData.targetCollections,
};
const publicForms = this.db.getRepository('publicForms');
const instance = await publicForms.findOne({
filter: {
key: tokenData.formKey,
},
});
if (!instance) {
throw new Error('The form is not found');
}
if (!instance.get('enabled')) {
throw new Error('The form is not enabled');
}
// 将 publicSubmit 转为 create用于触发工作流的 Action 事件)
const actionName = ctx.action.actionName;
if (actionName === 'publicSubmit') {
ctx.action.actionName = 'create';
}
} catch (error) {
ctx.throw(401, error.message);
}
}
await next();
};
parseACL = async (ctx, next) => {
if (!ctx.PublicForm) {
return next();
}
const { resourceName, actionName } = ctx.action;
const collection = this.db.getCollection(resourceName);
if (actionName === 'create' && ctx.PublicForm['collectionName'] === resourceName) {
ctx.permission = {
skip: true,
};
} else if (
(actionName === 'list' && ctx.PublicForm['targetCollections'].includes(resourceName)) ||
(collection.options.template === 'file' && actionName === 'create') ||
(resourceName === 'storages' && actionName === 'getRules') ||
(resourceName === 'map-configuration' && actionName === 'get')
) {
ctx.permission = {
skip: true,
};
}
await next();
};
async load() {
this.app.acl.registerSnippet({
name: `pm.${this.name}`,
actions: ['publicForms:*'],
});
this.app.acl.allow('publicForms', 'getMeta', 'public');
this.app.resourceManager.registerActionHandlers({
'publicForms:getMeta': this.getPublicFormsMeta,
});
this.app.dataSourceManager.afterAddDataSource((dataSource) => {
dataSource.resourceManager.use(this.parseToken, {
before: 'acl',
});
dataSource.acl.use(this.parseACL, {
before: 'core',
});
dataSource.resourceManager.registerActionHandlers({
publicSubmit: dataSource.resourceManager.getRegisteredHandler('create'),
});
});
}
async install() {}
async afterEnable() {}
async afterDisable() {}
async remove() {}
}
export default PluginPublicFormsServer;