refactor: remove demo (#1426)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#1426
This commit is contained in:
parent
7b742cb631
commit
84eae18c5c
@ -1,239 +0,0 @@
|
|||||||
import React, { useContext, useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
CollectionProvider,
|
|
||||||
css,
|
|
||||||
Grid,
|
|
||||||
i18n,
|
|
||||||
Plugin,
|
|
||||||
SchemaComponent,
|
|
||||||
SchemaComponentContext,
|
|
||||||
useAPIClient,
|
|
||||||
useRequest,
|
|
||||||
useTranslation,
|
|
||||||
useViewport,
|
|
||||||
} from '@tachybase/client';
|
|
||||||
import { FormItem, FormLayout } from '@tachybase/components';
|
|
||||||
import { formSchema } from '@tachybase/plugin-multi-app-manager/client';
|
|
||||||
import { createForm, useField, useForm } from '@tachybase/schema';
|
|
||||||
|
|
||||||
import { Alert } from 'antd';
|
|
||||||
|
|
||||||
const ft = 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js';
|
|
||||||
const sceneId = 'jruas0rs';
|
|
||||||
const prefix = '1bze12';
|
|
||||||
const changeLang = (locale = 'zh-CN') => {
|
|
||||||
const e = { language: 'cn', region: 'cn' };
|
|
||||||
return { 'zh-CN': e, 'en-US': { language: 'en', region: 'sgp' } }[locale] || e;
|
|
||||||
};
|
|
||||||
const Captcha = (t) => {
|
|
||||||
const { request, onSuccess, onError, language = 'zh-CN' } = t;
|
|
||||||
const getInstance = () => {};
|
|
||||||
const captchaVerifyCallback = async (param) => {
|
|
||||||
try {
|
|
||||||
return await request({ captchaVerifyParam: param, sceneId }), { captchaResult: true };
|
|
||||||
} catch (s) {
|
|
||||||
return onError == null || onError(s), { captchaResult: false };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onBizResultCallback = () => {
|
|
||||||
onSuccess == null || onSuccess();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const dynamicScript = document.createElement('script');
|
|
||||||
dynamicScript.type = 'text/javascript';
|
|
||||||
dynamicScript.src = ft;
|
|
||||||
dynamicScript.onload = () => {
|
|
||||||
// @ts-ignore
|
|
||||||
window.initAliyunCaptcha({
|
|
||||||
SceneId: sceneId,
|
|
||||||
prefix: prefix,
|
|
||||||
mode: 'embed',
|
|
||||||
element: '#captcha-element',
|
|
||||||
button: '#deploy',
|
|
||||||
captchaVerifyCallback: captchaVerifyCallback,
|
|
||||||
onBizResultCallback: onBizResultCallback,
|
|
||||||
getInstance: getInstance,
|
|
||||||
slideStyle: { width: 360, height: 40 },
|
|
||||||
...changeLang(language),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
document.head.appendChild(dynamicScript);
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="captcha-element"
|
|
||||||
className={css`
|
|
||||||
margin-top: 24px;
|
|
||||||
#aliyunCaptcha-window-embed {
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
#aliyunCaptcha-sliding-body {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
i18n.addResources('zh-CN', 'demo', {
|
|
||||||
'Deploy a demo site': '开通 tachybase 体验站点',
|
|
||||||
Email: '电子邮箱',
|
|
||||||
Yes: '是',
|
|
||||||
No: '否',
|
|
||||||
'Application template': '应用模板',
|
|
||||||
'Are you a developer': '你是一名开发者吗',
|
|
||||||
'Deploy Now': '立即开通',
|
|
||||||
Industry: '所在行业',
|
|
||||||
'Please fill in the necessary information and the demo site will be automatically created within one minute.':
|
|
||||||
'请填写必要的信息,体验站将在一分钟内自动创建成功。',
|
|
||||||
'Demo site deployed successfully!': '体验站点开通成功!',
|
|
||||||
'What you want to do with tachybase': '你希望用 tachybase 来做什么',
|
|
||||||
'Used to receive demo site information, please be sure to fill in correctly': '用于接收体验站点信息,请务必正确填写',
|
|
||||||
'Please briefly describe the purpose': '请简要描述用途,方便我们后续针对性的开发',
|
|
||||||
'Please briefly describe your industry': '请简要描述你所在的行业,方便我们后续针对性的开发',
|
|
||||||
});
|
|
||||||
const DemoProvider = React.createContext(null);
|
|
||||||
function useApplicationAction() {
|
|
||||||
const api = useAPIClient();
|
|
||||||
const form = useForm();
|
|
||||||
const field = useField();
|
|
||||||
const [, setSuccess] = useContext(DemoProvider);
|
|
||||||
return {
|
|
||||||
async run() {
|
|
||||||
await form.submit();
|
|
||||||
field.data = field.data || {};
|
|
||||||
field.data.loading = true;
|
|
||||||
try {
|
|
||||||
await api.request({ url: 'applicationForms:create', method: 'post', data: form.values });
|
|
||||||
field.data.loading = false;
|
|
||||||
setSuccess(true);
|
|
||||||
} catch (a) {
|
|
||||||
field.data.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export function DemoApplyForm() {
|
|
||||||
useViewport();
|
|
||||||
const schemaComponentContext = React.useContext(SchemaComponentContext);
|
|
||||||
const [isSuccess, setSuccess] = React.useState(false);
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
const api = useAPIClient();
|
|
||||||
// @ts-ignore
|
|
||||||
const { t, i18n } = useTranslation('demo');
|
|
||||||
const form = createForm();
|
|
||||||
const { data, loading: isLoading } = useRequest({ url: 'applicationForms:getTemplateNameUiSchema' });
|
|
||||||
const onRequest = async (requestData) => {
|
|
||||||
await form.validate();
|
|
||||||
await api.request({ url: 'applicationForms:create', method: 'post', data: { ...form.values, ...requestData } });
|
|
||||||
};
|
|
||||||
return isLoading ? null : (
|
|
||||||
<div style={{ margin: '0 auto', paddingTop: '15vh', paddingBottom: 50, maxWidth: 350 }}>
|
|
||||||
<title>{[t('Deploy a demo site'), ' - tachybase']}</title>
|
|
||||||
{isSuccess ? (
|
|
||||||
<Alert
|
|
||||||
message={t('Demo site deployed successfully!')}
|
|
||||||
description={
|
|
||||||
i18n.language === 'zh-CN' ? (
|
|
||||||
<div>
|
|
||||||
你将在几秒钟内收到邮件,其中包含体验站点的地址、账号和 密码。
|
|
||||||
<br />
|
|
||||||
该站点仅用于预览,你在体验站点所作的操作、所存储的数据 可能会丢失。
|
|
||||||
<br />
|
|
||||||
该站点将会在 48 小时后失效,之后你可以重新申请体验站点。
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
You will receive an email in a few seconds with the URL, username and password of the demo site.
|
|
||||||
<br />
|
|
||||||
The site is for preview purposes only, your actions and data stored on the site may be lost.
|
|
||||||
<br />
|
|
||||||
The site will expire after 48 hours, after which you can reapply for the site.
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
type="success"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
<DemoProvider.Provider value={[isSuccess, setSuccess]}>
|
|
||||||
<CollectionProvider name={(data as any)?.data?.collection}>
|
|
||||||
<SchemaComponentContext.Provider value={{ ...schemaComponentContext, designable: false }}>
|
|
||||||
<SchemaComponent
|
|
||||||
components={{
|
|
||||||
FormLayout,
|
|
||||||
FormItem,
|
|
||||||
Grid,
|
|
||||||
Captcha,
|
|
||||||
}}
|
|
||||||
scope={{ useApplicationAction }}
|
|
||||||
schema={{
|
|
||||||
name: 'appform',
|
|
||||||
type: 'object',
|
|
||||||
'x-component': 'FormV2',
|
|
||||||
'x-use-component-props': () => ({ form: form }),
|
|
||||||
properties: {
|
|
||||||
layout: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'FormLayout',
|
|
||||||
'x-component-props': { layout: 'vertical' },
|
|
||||||
properties: { div: (data as any)?.data?.schema },
|
|
||||||
},
|
|
||||||
captcha: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Captcha',
|
|
||||||
'x-component-props': {
|
|
||||||
request: onRequest,
|
|
||||||
language: i18n.language,
|
|
||||||
onSuccess: () => {
|
|
||||||
setSuccess(true), setLoading(false);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
setLoading(false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'div',
|
|
||||||
'x-component-props': { id: 'deploy' },
|
|
||||||
properties: {
|
|
||||||
submit: {
|
|
||||||
type: 'void',
|
|
||||||
title: t('Deploy Now'),
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
|
||||||
block: true,
|
|
||||||
loading: loading,
|
|
||||||
type: 'primary',
|
|
||||||
style: { width: '100%' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SchemaComponentContext.Provider>
|
|
||||||
</CollectionProvider>
|
|
||||||
</DemoProvider.Provider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export class PluginDemo extends Plugin {
|
|
||||||
async load() {
|
|
||||||
formSchema.properties['options.isTemplate'] = {
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-content': 'As an application template',
|
|
||||||
};
|
|
||||||
formSchema.properties['options.persistent'] = {
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-content': 'Persistent',
|
|
||||||
};
|
|
||||||
this.router.add('new', { path: '/new', Component: DemoApplyForm });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
import { defineCollection } from '@tachybase/database';
|
|
||||||
|
|
||||||
export default defineCollection({
|
|
||||||
name: 'applicationForms',
|
|
||||||
title: '申请表单',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'application',
|
|
||||||
type: 'belongsTo',
|
|
||||||
target: 'applications',
|
|
||||||
foreignKey: 'applicationName',
|
|
||||||
targetKey: 'name',
|
|
||||||
onDelete: 'SET NULL',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'templateName',
|
|
||||||
type: 'string',
|
|
||||||
interface: 'select',
|
|
||||||
uiSchema: {
|
|
||||||
title: '模板',
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'url',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'email',
|
|
||||||
type: 'string',
|
|
||||||
interface: 'input',
|
|
||||||
uiSchema: {
|
|
||||||
type: 'string',
|
|
||||||
title: '邮箱',
|
|
||||||
'x-component': 'Input',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'note',
|
|
||||||
type: 'string',
|
|
||||||
interface: 'textarea',
|
|
||||||
uiSchema: {
|
|
||||||
type: 'string',
|
|
||||||
title: '你希望用来做什么',
|
|
||||||
'x-component': 'Input.TextArea',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'industry',
|
|
||||||
interface: 'input',
|
|
||||||
type: 'string',
|
|
||||||
uiSchema: {
|
|
||||||
title: '所在行业',
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'isDeveloper',
|
|
||||||
interface: 'radioGroup',
|
|
||||||
type: 'string',
|
|
||||||
uiSchema: {
|
|
||||||
title: '你是一名开发者吗?',
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
enum: [
|
|
||||||
{
|
|
||||||
value: 'yes',
|
|
||||||
label: '是',
|
|
||||||
color: 'geekblue',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'no',
|
|
||||||
label: '不是',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,50 +0,0 @@
|
|||||||
function notificationTemplate(lang) {
|
|
||||||
return {
|
|
||||||
'zh-CN': {
|
|
||||||
name: 'demo',
|
|
||||||
subject: 'TachyBase 体验站点开通成功!',
|
|
||||||
body: `
|
|
||||||
<p>地址:https://<%= appId %>.v7.demo-cn.tachybase.com/</p>
|
|
||||||
<p>账号:admin@tachybase.com</p>
|
|
||||||
<p>密码:admin123</p>
|
|
||||||
<p>该站点仅用于体验,你在体验站点所做的操作、所存储的数据可能会丢失。</p>
|
|
||||||
<p>该站点将会在 48 小时后失效,之后你可以重新申请体验站点。</p>
|
|
||||||
`,
|
|
||||||
receiver_options: {},
|
|
||||||
},
|
|
||||||
'en-US': {
|
|
||||||
name: 'demo',
|
|
||||||
subject: 'TachyBase demo site deployed successfully!',
|
|
||||||
body: `
|
|
||||||
<p>URL: https://<%= appId %>.v7.demo.tachybase.com/</p>
|
|
||||||
<p>Account: admin@tachybase.com</p>
|
|
||||||
<p>Password: admin123</p>
|
|
||||||
<p>The site is for preview purposes only, your actions and data stored on the site may be lost.</p>
|
|
||||||
<p>The site will expire after 48 hours, after which you can reapply for the site.</p>
|
|
||||||
`,
|
|
||||||
receiver_options: {},
|
|
||||||
},
|
|
||||||
}[lang || 'en-US'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createNotificationRecord(app) {
|
|
||||||
await app.db.getRepository('notifications').create({
|
|
||||||
values: {
|
|
||||||
...notificationTemplate(app.i18n.language || 'en-US'),
|
|
||||||
service: {
|
|
||||||
type: 'email',
|
|
||||||
title: '阿里云邮件推送',
|
|
||||||
options: {
|
|
||||||
auth: {
|
|
||||||
pass: process.env.ALIYUN_MAIL_AUTH_PASS,
|
|
||||||
user: 'noreply@demo.tachybase.com',
|
|
||||||
},
|
|
||||||
from: 'TachyBase<noreply@demo.tachybase.com>',
|
|
||||||
host: 'smtpdm.aliyun.com',
|
|
||||||
port: 465,
|
|
||||||
secure: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,306 +0,0 @@
|
|||||||
import { resolve } from 'path';
|
|
||||||
import { CollectionRepository } from '@tachybase/plugin-collection-manager';
|
|
||||||
import PluginMultiAppManager from '@tachybase/plugin-multi-app-manager';
|
|
||||||
import { UiSchemaRepository } from '@tachybase/plugin-ui-schema-storage';
|
|
||||||
import { AppSupervisor, Gateway, Plugin } from '@tachybase/server';
|
|
||||||
import { uid } from '@tachybase/utils';
|
|
||||||
|
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Sequelize } from 'sequelize';
|
|
||||||
|
|
||||||
import { createNotificationRecord } from './create-notification';
|
|
||||||
import { createAliCaptchaClient, createAliCaptchaRequest } from './services';
|
|
||||||
|
|
||||||
const userApplicationPrefix = 'a_';
|
|
||||||
|
|
||||||
export class PluginDemo extends Plugin {
|
|
||||||
async afterAdd() {
|
|
||||||
this.app.resourcer.use(async (ctx, next) => {
|
|
||||||
if (this.app.name !== 'main' && ctx.action.actionName === 'disable' && ctx.action.resourceName === 'pm') {
|
|
||||||
const { filterByTk } = ctx.action.params;
|
|
||||||
if (['disable-pm-add', '@tachybase/plugin-disable-pm-add'].includes(filterByTk)) {
|
|
||||||
ctx.throw(403, 'No permissions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
if (this.app.name === 'main') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ctx.action.actionName === 'list' && ctx.action.resourceName === 'pm') {
|
|
||||||
ctx.body = ctx.body?.filter((item) => {
|
|
||||||
return ![
|
|
||||||
'@tachybase/plugin-verdaccio',
|
|
||||||
'@tachybase/plugin-demo-platform',
|
|
||||||
'@tachybase/plugin-disable-pm-add',
|
|
||||||
'@tachybase/plugin-notifications',
|
|
||||||
'@tachybase/plugin-multi-app-manager',
|
|
||||||
'@tachybase/plugin-multi-app-share-collection',
|
|
||||||
].includes(item.packageName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async beforeLoad() {
|
|
||||||
this.app.cronJobManager.addJob({
|
|
||||||
cronTime: '0 0 * * *',
|
|
||||||
onTick: async () => {
|
|
||||||
await this.cleanExpiredApplications();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.app.cronJobManager.addJob({
|
|
||||||
cronTime: '0 */30 * * * *',
|
|
||||||
onTick: async () => {
|
|
||||||
const expiredAt = new Date(new Date().getTime() - 30 * 60 * 1e3);
|
|
||||||
await this.removeAppInMemoryThatNotActiveAfterTime(expiredAt);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.db.on('applications.beforeCreate', async (model) => {
|
|
||||||
if (!model.name) {
|
|
||||||
model.set('name', `${userApplicationPrefix}${uid()}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.db.on('applications.afterDestroy', async (model, options) => {
|
|
||||||
const transaction = options.transaction;
|
|
||||||
const deleteDatabase = async () => {
|
|
||||||
const sql = `DROP DATABASE IF EXISTS "${model.get('name')}"`;
|
|
||||||
await this.db.sequelize.query(sql);
|
|
||||||
};
|
|
||||||
if (!transaction) {
|
|
||||||
await deleteDatabase();
|
|
||||||
} else {
|
|
||||||
transaction.afterCommit(async () => {
|
|
||||||
await deleteDatabase();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.db.on('applicationForms.beforeCreate', async (model, options) => {
|
|
||||||
if (!model.get('templateName')) {
|
|
||||||
throw new Error('templateName is required');
|
|
||||||
}
|
|
||||||
if (!model.get('email')) {
|
|
||||||
throw new Error('email is required');
|
|
||||||
}
|
|
||||||
model.set('applicationName', `${userApplicationPrefix}${uid()}`);
|
|
||||||
model.set('url', `https://${model.get('applicationName')}.${process.env.DEMO_HOST}`);
|
|
||||||
});
|
|
||||||
this.db.on('applicationForms.afterCreate', async (model, options) => {
|
|
||||||
const { transaction } = options;
|
|
||||||
transaction.afterCommit(() => {
|
|
||||||
this.db.sequelize.transaction(async (newTransaction) => {
|
|
||||||
try {
|
|
||||||
const application = await this.db.getRepository('applications').create({
|
|
||||||
transaction: newTransaction,
|
|
||||||
values: {
|
|
||||||
name: model.get('applicationName'),
|
|
||||||
options: {
|
|
||||||
templateName: model.get('templateName'),
|
|
||||||
authManager: {
|
|
||||||
jwt: {
|
|
||||||
secret: nanoid(48),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
templateName: model.get('templateName'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await model.setApplication(application, { transaction: newTransaction });
|
|
||||||
const notification = await this.db.getRepository('notifications').findOne({ transaction: newTransaction });
|
|
||||||
notification
|
|
||||||
.send({
|
|
||||||
to: model.get('email'),
|
|
||||||
appId: application.get('name'),
|
|
||||||
})
|
|
||||||
.catch((e) => console.log(e));
|
|
||||||
} catch (e) {
|
|
||||||
this.app.log.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.app.resourcer.registerActionHandler('updateTpl', async (ctx, next) => {
|
|
||||||
const { resourceName } = ctx.action;
|
|
||||||
if (resourceName !== 'applications') {
|
|
||||||
return await next();
|
|
||||||
}
|
|
||||||
const { filterByTk } = ctx.action.params;
|
|
||||||
const applicationName = filterByTk;
|
|
||||||
if (!applicationName.startsWith('demo')) {
|
|
||||||
throw new Error('Only template application can be update to tpl');
|
|
||||||
}
|
|
||||||
const templateName = `${applicationName}_tpl`;
|
|
||||||
await ctx.db.sequelize.query(`DROP DATABASE IF EXISTS "${templateName}"`);
|
|
||||||
await ctx.db.sequelize.query(`CREATE DATABASE "${templateName}" TEMPLATE "${applicationName}"`);
|
|
||||||
ctx.body = 'ok';
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async cleanExpiredApplications() {
|
|
||||||
const now = new Date();
|
|
||||||
const expiredTime = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1e3);
|
|
||||||
const applications = await this.db.getRepository('applications').find({
|
|
||||||
filter: {
|
|
||||||
'createdAt.$lte': expiredTime,
|
|
||||||
// 创建时间超过两天
|
|
||||||
'options.templateName.$ne': null,
|
|
||||||
// 非模板应用
|
|
||||||
'options.persistent.$not': true,
|
|
||||||
// 非持久化应用
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await this.db.getRepository('applications').destroy({
|
|
||||||
filterByTk: applications.map((application) => application.get('name')),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async removeAppInMemoryThatNotActiveAfterTime(expiredAt) {
|
|
||||||
const expiredAtInUnix = Math.floor(expiredAt.getTime() / 1e3);
|
|
||||||
const applications = await this.db.getRepository('applications').find({
|
|
||||||
filter: {
|
|
||||||
'options.templateName.$ne': null,
|
|
||||||
'options.persistent.$not': true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
for (const application of applications) {
|
|
||||||
const lastSeenAt = AppSupervisor.getInstance().lastSeenAt.get(application.get('name'));
|
|
||||||
if (lastSeenAt && lastSeenAt < expiredAtInUnix) {
|
|
||||||
await AppSupervisor.getInstance().removeApp(application.get('name'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async load() {
|
|
||||||
await this.db.import({ directory: resolve(__dirname, 'collections') });
|
|
||||||
Gateway.getInstance().addAppSelectorMiddleware(async (ctx, next) => {
|
|
||||||
if (!ctx.resolvedAppName) {
|
|
||||||
const req = ctx.req;
|
|
||||||
const headers = req.headers;
|
|
||||||
const hostname = headers['x-hostname'] || headers['host'];
|
|
||||||
const firstPart = hostname.split('.')[0];
|
|
||||||
if (firstPart.startsWith(userApplicationPrefix)) {
|
|
||||||
ctx.resolvedAppName = firstPart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
this.app.resourcer.registerActionHandler('applicationForms:getTemplateNameUiSchema', async (ctx, next) => {
|
|
||||||
const uiSchemaTemplates = this.db.getRepository('uiSchemaTemplates');
|
|
||||||
const uiSchemas = this.db.getRepository<UiSchemaRepository>('uiSchemas');
|
|
||||||
const collection = this.db.getRepository('collections');
|
|
||||||
const instance = await uiSchemaTemplates.findOne({
|
|
||||||
filter: {
|
|
||||||
name: 'demo',
|
|
||||||
collectionName: 'applicationForms',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
ctx.body = {
|
|
||||||
collection: await collection.findOne({
|
|
||||||
filter: {
|
|
||||||
name: 'applicationForms',
|
|
||||||
},
|
|
||||||
appends: ['fields'],
|
|
||||||
}),
|
|
||||||
schema: await uiSchemas.getJsonSchema(instance['uid']),
|
|
||||||
};
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
this.app.acl.allow('applicationForms', 'getTemplateNameUiSchema', 'public');
|
|
||||||
this.app.acl.allow('applicationForms', 'create', 'public');
|
|
||||||
const aliCaptchaClient = createAliCaptchaClient();
|
|
||||||
this.app.resourcer.use(async (ctx, next) => {
|
|
||||||
if (ctx.action.actionName === 'create' && ctx.action.resourceName === 'applicationForms') {
|
|
||||||
const { senceId } = ctx.action.params;
|
|
||||||
const { captchaVerifyParam } = ctx.action.params.values;
|
|
||||||
delete ctx.action.params.values.captchaVerifyParam;
|
|
||||||
const request = createAliCaptchaRequest({
|
|
||||||
senceId,
|
|
||||||
captchaVerifyParam,
|
|
||||||
});
|
|
||||||
const resp = await aliCaptchaClient.verifyIntelligentCaptcha(request);
|
|
||||||
const captchaVerifyResult = resp.body.result.verifyResult;
|
|
||||||
if (!captchaVerifyResult) {
|
|
||||||
ctx.throw(400, 'captcha verify failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
const multiAppManager = this.pm.get('multi-app-manager') as PluginMultiAppManager;
|
|
||||||
multiAppManager.setAppDbCreator(async (app, options) => {
|
|
||||||
const { context = {} } = options;
|
|
||||||
const databaseOptions = app.options.database;
|
|
||||||
// @ts-ignore
|
|
||||||
const { host, port, username, password, database } = databaseOptions;
|
|
||||||
const { templateName } = context;
|
|
||||||
const client = new Sequelize({
|
|
||||||
dialect: 'postgres',
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
database: 'postgres',
|
|
||||||
});
|
|
||||||
if (templateName) {
|
|
||||||
let retryCount = 0;
|
|
||||||
let success = false;
|
|
||||||
const maxRetries = 5;
|
|
||||||
const retryDelay = 1e3;
|
|
||||||
while (retryCount < maxRetries && !success) {
|
|
||||||
try {
|
|
||||||
const res = await client.query(`CREATE DATABASE "${database}" TEMPLATE "${templateName}_tpl"`);
|
|
||||||
success = true;
|
|
||||||
console.log('Database created successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create database:', error);
|
|
||||||
retryCount++;
|
|
||||||
console.log(`Retrying (${retryCount})...`);
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
throw new Error('Failed to create database after multiple retries');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await client.query(`DROP DATABASE IF EXISTS "${database}"`);
|
|
||||||
await client.query(`CREATE DATABASE "${database}"`);
|
|
||||||
}
|
|
||||||
await client.close();
|
|
||||||
});
|
|
||||||
multiAppManager.setSubAppUpgradeHandler(async (mainApp) => {
|
|
||||||
const templateApps = await mainApp.db.getRepository('applications').find({
|
|
||||||
filter: {
|
|
||||||
'options.isTemplate': true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const appSupervisor = AppSupervisor.getInstance();
|
|
||||||
for (const instance of templateApps) {
|
|
||||||
const subApp = await appSupervisor.getApp(instance.name, {
|
|
||||||
upgrading: true,
|
|
||||||
});
|
|
||||||
await subApp.runAsCLI(['upgrade'], { from: 'user' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async install(options) {
|
|
||||||
if (!this.db.inDialect('postgres')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.pm.get('multi-app-manager').enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await createNotificationRecord(this.app);
|
|
||||||
const repo = this.db.getRepository<CollectionRepository>('collections');
|
|
||||||
if (repo) {
|
|
||||||
await repo.db2cm('applicationForms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async beforeEnable() {
|
|
||||||
if (!this.db.inDialect('postgres')) {
|
|
||||||
throw new Error('demo-platform plugin only support postgres');
|
|
||||||
}
|
|
||||||
if (!this.pm.get('multi-app-manager')?.enabled) {
|
|
||||||
throw new Error(`${this.name} plugin need multi-app-manager plugin enabled`);
|
|
||||||
}
|
|
||||||
if (!this.pm.get('notifications')?.enabled && !this.pm.get('@tachybase/plugin-notifications')?.enabled) {
|
|
||||||
throw new Error(`${this.name} plugin need notifications plugin enabled`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import Captcha, { VerifyIntelligentCaptchaRequest } from '@alicloud/captcha20230305';
|
|
||||||
import { Config } from '@alicloud/openapi-client';
|
|
||||||
|
|
||||||
export function createAliCaptchaClient() {
|
|
||||||
const config = new Config({});
|
|
||||||
config.accessKeyId = process.env['ALI_CAPTCHA_ACCESS_KEY_ID'];
|
|
||||||
config.accessKeySecret = process.env['ALI_CAPTCHA_ACCESS_KEY_SECRET'];
|
|
||||||
config.endpoint = process.env['ALI_CAPTCH_ENDPOINT']
|
|
||||||
? process.env['ALI_CAPTCH_ENDPOINT']
|
|
||||||
: 'captcha.cn-shanghai.aliyuncs.com';
|
|
||||||
config.connectTimeout = 5e3;
|
|
||||||
config.readTimeout = 5e3;
|
|
||||||
return new Captcha(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createAliCaptchaRequest({ senceId, captchaVerifyParam }) {
|
|
||||||
const request = new VerifyIntelligentCaptchaRequest({});
|
|
||||||
request.sceneId = senceId;
|
|
||||||
request.captchaVerifyParam = captchaVerifyParam;
|
|
||||||
return request;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user