feat: improve code
This commit is contained in:
parent
a51c5058fb
commit
27f6bde775
@ -10,7 +10,7 @@
|
|||||||
"examples": "ts-node-dev -r dotenv/config ./examples",
|
"examples": "ts-node-dev -r dotenv/config ./examples",
|
||||||
"start": "cd packages/app && npm start",
|
"start": "cd packages/app && npm start",
|
||||||
"start-client": "cd packages/app && npm run start-client",
|
"start-client": "cd packages/app && npm run start-client",
|
||||||
"start-server": "nodemon",
|
"start-server": "ts-node-dev -r dotenv/config ./packages/api/src/index.ts",
|
||||||
"start-docs": "dumi dev",
|
"start-docs": "dumi dev",
|
||||||
"build-docs": "dumi build",
|
"build-docs": "dumi build",
|
||||||
"build2": "lerna run build",
|
"build2": "lerna run build",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/file-saver": "^2.0.3",
|
"@types/file-saver": "^2.0.3",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/koa": "^2.13.1",
|
"@types/koa": "^2.13.1",
|
||||||
"@types/koa-mount": "^4.0.1",
|
"@types/koa-mount": "^4.0.1",
|
||||||
"@types/lodash": "^4.14.169",
|
"@types/lodash": "^4.14.169",
|
||||||
|
@ -1,15 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const keys = process.argv;
|
const keys = process.argv;
|
||||||
|
|
||||||
const key = keys.pop();
|
|
||||||
|
|
||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
require('../lib/index');
|
||||||
if (key === 'start') {
|
|
||||||
require('../lib/index');
|
|
||||||
} else if (key === 'db-init') {
|
|
||||||
require('../lib/migrations/init');
|
|
||||||
}
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import Server from '@nocobase/server';
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const sync = global.sync || {
|
|
||||||
force: false,
|
|
||||||
alter: {
|
|
||||||
drop: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('process.env.NOCOBASE_ENV', process.env.NOCOBASE_ENV);
|
|
||||||
|
|
||||||
const api = new Server({
|
|
||||||
database: {
|
|
||||||
username: process.env.DB_USER,
|
|
||||||
password: process.env.DB_PASSWORD,
|
|
||||||
database: process.env.DB_DATABASE,
|
|
||||||
host: process.env.DB_HOST,
|
|
||||||
port: process.env.DB_PORT as any,
|
|
||||||
dialect: process.env.DB_DIALECT as any,
|
|
||||||
dialectOptions: {
|
|
||||||
charset: 'utf8mb4',
|
|
||||||
collate: 'utf8mb4_unicode_ci',
|
|
||||||
},
|
|
||||||
pool: {
|
|
||||||
max: 5,
|
|
||||||
min: 0,
|
|
||||||
acquire: 60000,
|
|
||||||
idle: 10000,
|
|
||||||
},
|
|
||||||
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
|
|
||||||
define: {},
|
|
||||||
sync,
|
|
||||||
},
|
|
||||||
resourcer: {
|
|
||||||
prefix: '/api',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
'@nocobase/plugin-collections',
|
|
||||||
'@nocobase/plugin-ui-router',
|
|
||||||
'@nocobase/plugin-ui-schema',
|
|
||||||
'@nocobase/plugin-users',
|
|
||||||
'@nocobase/plugin-action-logs',
|
|
||||||
'@nocobase/plugin-file-manager',
|
|
||||||
'@nocobase/plugin-permissions',
|
|
||||||
'@nocobase/plugin-export',
|
|
||||||
'@nocobase/plugin-system-settings',
|
|
||||||
// // '@nocobase/plugin-automations',
|
|
||||||
'@nocobase/plugin-china-region',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
api.registerPlugin(plugin, [require(`${plugin}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`).default]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default api;
|
|
@ -1,30 +1,65 @@
|
|||||||
import api from './app';
|
import Server from '@nocobase/server';
|
||||||
import { middlewares } from '@nocobase/server';
|
|
||||||
|
const api = new Server({
|
||||||
|
database: {
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_DATABASE,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT as any,
|
||||||
|
dialect: process.env.DB_DIALECT as any,
|
||||||
|
dialectOptions: {
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
collate: 'utf8mb4_unicode_ci',
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
acquire: 60000,
|
||||||
|
idle: 10000,
|
||||||
|
},
|
||||||
|
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
|
||||||
|
define: {},
|
||||||
|
sync: {
|
||||||
|
force: false,
|
||||||
|
alter: {
|
||||||
|
drop: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resourcer: {
|
||||||
|
prefix: '/api',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
'@nocobase/plugin-collections',
|
||||||
|
'@nocobase/plugin-ui-router',
|
||||||
|
'@nocobase/plugin-ui-schema',
|
||||||
|
'@nocobase/plugin-users',
|
||||||
|
'@nocobase/plugin-action-logs',
|
||||||
|
'@nocobase/plugin-file-manager',
|
||||||
|
'@nocobase/plugin-permissions',
|
||||||
|
'@nocobase/plugin-export',
|
||||||
|
'@nocobase/plugin-system-settings',
|
||||||
|
'@nocobase/plugin-china-region',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
api.plugin(require(`${plugin}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`).default);
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
api.resourcer.use(middlewares.actionParams());
|
|
||||||
|
|
||||||
api.on('plugins.afterLoad', async () => {
|
|
||||||
console.log('plugins.afterLoad')
|
|
||||||
if (process.env.NOCOBASE_ENV === 'demo') {
|
|
||||||
api.resourcer.use(middlewares.demoBlacklistedActions({
|
|
||||||
emails: [process.env.ADMIN_EMAIL],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
api.use(middlewares.appDistServe({
|
|
||||||
root: process.env.APP_DIST,
|
|
||||||
useStaticServer: !(process.env.APP_USE_STATIC_SERVER === 'false' || !process.env.APP_USE_STATIC_SERVER),
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
if (process.argv.length < 3) {
|
if (process.argv.length < 3) {
|
||||||
process.argv.push('start', '--port', process.env.API_PORT);
|
process.argv.push('start', '--port', process.env.API_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.start(process.argv);
|
console.log(process.argv);
|
||||||
|
|
||||||
|
await api.parse(process.argv);
|
||||||
console.log(api.db.getTables().map(t => t.getName()));
|
console.log(api.db.getTables().map(t => t.getName()));
|
||||||
console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
|
console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
|
||||||
console.log(`http://localhost:${process.env.API_PORT}/`);
|
// console.log(`http://localhost:${process.env.API_PORT}/`);
|
||||||
})();
|
})();
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
const keys = process.argv;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
global.sync = {
|
|
||||||
force: false,
|
|
||||||
alter: {
|
|
||||||
drop: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const filename: string = keys.pop();
|
|
||||||
|
|
||||||
require(`./migrations/${filename}`);
|
|
@ -1,125 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
global.sync = {
|
|
||||||
force: true,
|
|
||||||
alter: {
|
|
||||||
drop: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
import Database from '@nocobase/database';
|
|
||||||
import api from '../app';
|
|
||||||
import * as uiSchema from './ui-schema';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await api.loadPlugins();
|
|
||||||
const database: Database = api.db;
|
|
||||||
await database.sync({
|
|
||||||
// tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const config =
|
|
||||||
require('@nocobase/plugin-users/src/collections/users').default;
|
|
||||||
const Collection = database.getModel('collections');
|
|
||||||
const collection = await Collection.create(config);
|
|
||||||
await collection.updateAssociations({
|
|
||||||
generalFields: config.fields.filter((field) => field.state !== 0),
|
|
||||||
systemFields: config.fields.filter((field) => field.state === 0),
|
|
||||||
});
|
|
||||||
await collection.migrate();
|
|
||||||
|
|
||||||
const Route = database.getModel('routes');
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
type: 'redirect',
|
|
||||||
from: '/',
|
|
||||||
to: '/admin',
|
|
||||||
exact: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'route',
|
|
||||||
path: '/admin/:name(.+)?',
|
|
||||||
component: 'AdminLayout',
|
|
||||||
title: `后台`,
|
|
||||||
uiSchema: uiSchema.menu,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'route',
|
|
||||||
component: 'AuthLayout',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: 'route',
|
|
||||||
path: '/login',
|
|
||||||
component: 'RouteSchemaRenderer',
|
|
||||||
title: `登录`,
|
|
||||||
uiSchema: uiSchema.login,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'route',
|
|
||||||
path: '/register',
|
|
||||||
component: 'RouteSchemaRenderer',
|
|
||||||
title: `注册`,
|
|
||||||
uiSchema: uiSchema.register,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const item of data) {
|
|
||||||
const route = await Route.create(item);
|
|
||||||
await route.updateAssociations(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Storage = database.getModel('storages');
|
|
||||||
await Storage.create({
|
|
||||||
title: '本地存储',
|
|
||||||
name: `local`,
|
|
||||||
type: 'local',
|
|
||||||
baseUrl: process.env.LOCAL_STORAGE_BASE_URL,
|
|
||||||
default: process.env.STORAGE_TYPE === 'local',
|
|
||||||
});
|
|
||||||
await Storage.create({
|
|
||||||
name: `ali-oss`,
|
|
||||||
type: 'ali-oss',
|
|
||||||
baseUrl: process.env.ALI_OSS_STORAGE_BASE_URL,
|
|
||||||
options: {
|
|
||||||
region: process.env.ALI_OSS_REGION,
|
|
||||||
accessKeyId: process.env.ALI_OSS_ACCESS_KEY_ID,
|
|
||||||
accessKeySecret: process.env.ALI_OSS_ACCESS_KEY_SECRET,
|
|
||||||
bucket: process.env.ALI_OSS_BUCKET,
|
|
||||||
},
|
|
||||||
default: process.env.STORAGE_TYPE === 'ali-oss',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 导入地域数据
|
|
||||||
const ChinaRegion = database.getModel('china_regions');
|
|
||||||
ChinaRegion && (await ChinaRegion.importData());
|
|
||||||
|
|
||||||
const SystemSetting = database.getModel('system_settings');
|
|
||||||
if (SystemSetting) {
|
|
||||||
const setting = await SystemSetting.create({
|
|
||||||
title: 'NocoBase',
|
|
||||||
showLogoOnly: true,
|
|
||||||
});
|
|
||||||
await setting.updateAssociations({
|
|
||||||
logo: {
|
|
||||||
title: 'nocobase-logo',
|
|
||||||
filename: '682e5ad037dd02a0fe4800a3e91c283b.png',
|
|
||||||
extname: '.png',
|
|
||||||
mimetype: 'image/png',
|
|
||||||
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png',
|
|
||||||
storage_id: 2,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = database.getModel('users');
|
|
||||||
const user = await User.create({
|
|
||||||
nickname: '超级管理员',
|
|
||||||
email: process.env.ADMIN_EMAIL,
|
|
||||||
password: process.env.ADMIN_PASSWORD,
|
|
||||||
});
|
|
||||||
|
|
||||||
await database.close();
|
|
||||||
})();
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
import Database from '@nocobase/database';
|
|
||||||
import api from '../app';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await api.loadPlugins();
|
|
||||||
const database: Database = api.db;
|
|
||||||
await database.sync({
|
|
||||||
// tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
|
|
||||||
});
|
|
||||||
await database.close();
|
|
||||||
})();
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from './login';
|
|
||||||
export * from './menu';
|
|
||||||
export * from './register';
|
|
@ -1,64 +0,0 @@
|
|||||||
export const login = {
|
|
||||||
key: 'dtf9j0b8p9u',
|
|
||||||
type: 'object',
|
|
||||||
title: '登录',
|
|
||||||
properties: {
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '电子邮箱',
|
|
||||||
style: {
|
|
||||||
// width: 240,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Password',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '密码',
|
|
||||||
style: {
|
|
||||||
// width: 240,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Div',
|
|
||||||
properties: {
|
|
||||||
submit: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
|
||||||
block: true,
|
|
||||||
type: 'primary',
|
|
||||||
useAction: '{{ useLogin }}',
|
|
||||||
style: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: '登录',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registerlink: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Div',
|
|
||||||
properties: {
|
|
||||||
link: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-component-props': {
|
|
||||||
to: '/register',
|
|
||||||
},
|
|
||||||
title: '注册账号',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
export const menu = {
|
|
||||||
key: 'qqzzjakwkwl',
|
|
||||||
name: 'qqzzjakwkwl',
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Menu',
|
|
||||||
'x-designable-bar': 'Menu.DesignableBar',
|
|
||||||
'x-component-props': {
|
|
||||||
mode: 'mix',
|
|
||||||
theme: 'dark',
|
|
||||||
defaultSelectedKeys: '{{ selectedKeys }}',
|
|
||||||
sideMenuRef: '{{ sideMenuRef }}',
|
|
||||||
onSelect: '{{ onSelect }}',
|
|
||||||
onRemove: '{{ onMenuItemRemove }}',
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,100 +0,0 @@
|
|||||||
export const register = {
|
|
||||||
key: '46qlxqam3xk',
|
|
||||||
type: 'object',
|
|
||||||
title: '注册',
|
|
||||||
properties: {
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '电子邮箱',
|
|
||||||
style: {
|
|
||||||
// width: 240,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Password',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '密码',
|
|
||||||
checkStrength: true,
|
|
||||||
style: {
|
|
||||||
// width: 240,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.confirm_password'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
errors:
|
|
||||||
'{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
confirm_password: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Password',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '确认密码',
|
|
||||||
checkStrength: true,
|
|
||||||
style: {
|
|
||||||
// width: 240,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
dependencies: ['.password'],
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
errors:
|
|
||||||
'{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Div',
|
|
||||||
properties: {
|
|
||||||
submit: {
|
|
||||||
type: 'void',
|
|
||||||
title: '注册',
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
|
||||||
block: true,
|
|
||||||
type: 'primary',
|
|
||||||
useAction: '{{ useRegister }}',
|
|
||||||
style: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registerlink: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Div',
|
|
||||||
properties: {
|
|
||||||
link: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action.Link',
|
|
||||||
'x-component-props': {
|
|
||||||
to: '/login',
|
|
||||||
},
|
|
||||||
title: '使用已有账号登录',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -48,6 +48,32 @@ describe('has many field', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('custom sourceKey', async () => {
|
||||||
|
const collection = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'key', unique: true },
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'comments',
|
||||||
|
sourceKey: 'key',
|
||||||
|
// foreignKey: 'postKey',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const comments = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
schema: [],
|
||||||
|
});
|
||||||
|
const association = collection.model.associations.comments;
|
||||||
|
expect(association).toBeDefined();
|
||||||
|
expect(association.foreignKey).toBe('postKey');
|
||||||
|
// @ts-ignore
|
||||||
|
expect(association.sourceKey).toBe('key');
|
||||||
|
expect(comments.model.rawAttributes['postKey']).toBeDefined();
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
it('custom sourceKey and foreignKey', async () => {
|
it('custom sourceKey and foreignKey', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { Database } from '../../database';
|
||||||
|
import { mockDatabase } from '../';
|
||||||
|
|
||||||
|
describe('has many field', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = mockDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('association undefined', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
schema: [{ type: 'hasOne', name: 'profile' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
expect(User.model.associations.profile).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('association defined', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
schema: [{ type: 'hasOne', name: 'profile' }],
|
||||||
|
});
|
||||||
|
expect(User.model.associations.phone).toBeUndefined();
|
||||||
|
const Profile = db.collection({
|
||||||
|
name: 'profiles',
|
||||||
|
schema: [{ type: 'string', name: 'content' }],
|
||||||
|
});
|
||||||
|
const association = User.model.associations.profile;
|
||||||
|
expect(association).toBeDefined();
|
||||||
|
expect(association.foreignKey).toBe('userId');
|
||||||
|
// @ts-ignore
|
||||||
|
expect(association.sourceKey).toBe('id');
|
||||||
|
expect(Profile.model.rawAttributes['userId']).toBeDefined();
|
||||||
|
await db.sync();
|
||||||
|
// const post = await model.create<any>();
|
||||||
|
// await post.createComment({
|
||||||
|
// content: 'content111',
|
||||||
|
// });
|
||||||
|
// const postComments = await post.getComments();
|
||||||
|
// expect(postComments.map((comment) => comment.content)).toEqual([
|
||||||
|
// 'content111',
|
||||||
|
// ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('schema delete', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
schema: [{ type: 'hasOne', name: 'profile' }],
|
||||||
|
});
|
||||||
|
const Profile = db.collection({
|
||||||
|
name: 'profiles',
|
||||||
|
schema: [{ type: 'belongsTo', name: 'user' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
User.schema.delete('profile');
|
||||||
|
expect(User.model.associations.profile).toBeUndefined();
|
||||||
|
expect(Profile.model.rawAttributes.userId).toBeDefined();
|
||||||
|
Profile.schema.delete('user');
|
||||||
|
expect(Profile.model.rawAttributes.userId).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,70 @@
|
|||||||
|
import { Database } from '../../database';
|
||||||
|
import { mockDatabase } from '../';
|
||||||
|
import { SortField } from '../../schema-fields';
|
||||||
|
|
||||||
|
describe('string field', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = mockDatabase();
|
||||||
|
db.registerSchemaTypes({
|
||||||
|
sort: SortField
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sort', async () => {
|
||||||
|
const Test = db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
schema: [
|
||||||
|
{ type: 'sort', name: 'sort' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const test1 = await Test.model.create<any>();
|
||||||
|
expect(test1.sort).toBe(1);
|
||||||
|
const test2 = await Test.model.create<any>();
|
||||||
|
expect(test2.sort).toBe(2);
|
||||||
|
const test3 = await Test.model.create<any>();
|
||||||
|
expect(test3.sort).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skip if sort value not empty', async () => {
|
||||||
|
const Test = db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
schema: [
|
||||||
|
{ type: 'sort', name: 'sort' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const test1 = await Test.model.create<any>({ sort: 3 });
|
||||||
|
expect(test1.sort).toBe(3);
|
||||||
|
const test2 = await Test.model.create<any>();
|
||||||
|
expect(test2.sort).toBe(4);
|
||||||
|
const test3 = await Test.model.create<any>();
|
||||||
|
expect(test3.sort).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scopeKey', async () => {
|
||||||
|
const Test = db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
schema: [
|
||||||
|
{ type: 'sort', name: 'sort', scopeKey: 'status' },
|
||||||
|
{ type: 'string', name: 'status' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const t1 = await Test.model.create({ status: 'publish' });
|
||||||
|
const t2 = await Test.model.create({ status: 'publish' });
|
||||||
|
const t3 = await Test.model.create({ status: 'draft' });
|
||||||
|
const t4 = await Test.model.create({ status: 'draft' });
|
||||||
|
expect(t1.get('sort')).toBe(1);
|
||||||
|
expect(t2.get('sort')).toBe(2);
|
||||||
|
expect(t3.get('sort')).toBe(1);
|
||||||
|
expect(t4.get('sort')).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
101
packages/collections/src/__tests__/update-associations.test.ts
Normal file
101
packages/collections/src/__tests__/update-associations.test.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { Database } from '../database';
|
||||||
|
import { updateAssociation, updateAssociations } from '../update-associations';
|
||||||
|
import { mockDatabase } from './';
|
||||||
|
|
||||||
|
describe('update associations', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = mockDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasMany', () => {
|
||||||
|
it.only('model', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'title' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const user = await User.model.create();
|
||||||
|
const post1 = await Post.model.create();
|
||||||
|
const post2 = await Post.model.create<any>();
|
||||||
|
const post3 = await Post.model.create<any>();
|
||||||
|
const post4 = await Post.model.create<any>();
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: {
|
||||||
|
title: 'post0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: post1,
|
||||||
|
});
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: post2.id,
|
||||||
|
});
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: [post3.id],
|
||||||
|
});
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: {
|
||||||
|
id: post4.id,
|
||||||
|
title: 'post4',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'title' },
|
||||||
|
{ type: 'belongsTo', name: 'user' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
schema: [
|
||||||
|
{ type: 'string', name: 'content' },
|
||||||
|
{ type: 'belongsTo', name: 'post' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const user = await User.model.create();
|
||||||
|
await updateAssociations(user, {
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
title: 'post1',
|
||||||
|
// user: {
|
||||||
|
// name: 'user1',
|
||||||
|
// },
|
||||||
|
comments: [
|
||||||
|
{
|
||||||
|
content: 'content1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -29,12 +29,15 @@ export class Collection {
|
|||||||
const { name, tableName } = options;
|
const { name, tableName } = options;
|
||||||
this.model = class extends Model<any, any> {};
|
this.model = class extends Model<any, any> {};
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
|
// TODO: 不能重复 model.init,如果有涉及 InitOptions 参数修改,需要另外处理。
|
||||||
this.model.init(attributes, {
|
this.model.init(attributes, {
|
||||||
..._.omit(options, ['name', 'schema']),
|
..._.omit(options, ['name', 'schema']),
|
||||||
sequelize: context.database.sequelize,
|
sequelize: context.database.sequelize,
|
||||||
modelName: name,
|
modelName: name,
|
||||||
tableName: tableName || name,
|
tableName: tableName || name,
|
||||||
});
|
});
|
||||||
|
// schema 只针对字段,对应 Sequelize 的 Attributes
|
||||||
|
// 其他 InitOptions 参数放在 Collection 里,通过其他方法同步给 model
|
||||||
this.schema = new Schema(options.schema, {
|
this.schema = new Schema(options.schema, {
|
||||||
...context,
|
...context,
|
||||||
collection: this,
|
collection: this,
|
||||||
|
@ -4,6 +4,7 @@ import { Collection, CollectionOptions } from './collection';
|
|||||||
import {
|
import {
|
||||||
RelationField,
|
RelationField,
|
||||||
StringField,
|
StringField,
|
||||||
|
HasOneField,
|
||||||
HasManyField,
|
HasManyField,
|
||||||
BelongsToField,
|
BelongsToField,
|
||||||
BelongsToManyField,
|
BelongsToManyField,
|
||||||
@ -21,6 +22,8 @@ export type DatabaseOptions = Options | Sequelize;
|
|||||||
export class Database extends EventEmitter {
|
export class Database extends EventEmitter {
|
||||||
sequelize: Sequelize;
|
sequelize: Sequelize;
|
||||||
schemaTypes = new Map();
|
schemaTypes = new Map();
|
||||||
|
models = new Map();
|
||||||
|
repositories = new Map();
|
||||||
collections: Map<string, Collection>;
|
collections: Map<string, Collection>;
|
||||||
pendingFields = new Map<string, RelationField[]>();
|
pendingFields = new Map<string, RelationField[]>();
|
||||||
|
|
||||||
@ -42,6 +45,7 @@ export class Database extends EventEmitter {
|
|||||||
string: StringField,
|
string: StringField,
|
||||||
json: JsonField,
|
json: JsonField,
|
||||||
jsonb: JsonbField,
|
jsonb: JsonbField,
|
||||||
|
hasOne: HasOneField,
|
||||||
hasMany: HasManyField,
|
hasMany: HasManyField,
|
||||||
belongsTo: BelongsToField,
|
belongsTo: BelongsToField,
|
||||||
belongsToMany: BelongsToManyField,
|
belongsToMany: BelongsToManyField,
|
||||||
@ -72,9 +76,7 @@ export class Database extends EventEmitter {
|
|||||||
|
|
||||||
removePendingField(field: RelationField) {
|
removePendingField(field: RelationField) {
|
||||||
const items = this.pendingFields.get(field.target) || [];
|
const items = this.pendingFields.get(field.target) || [];
|
||||||
const index = items.findIndex(
|
const index = items.indexOf(field);
|
||||||
(item) => item && item.name === field.name,
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
delete items[index];
|
delete items[index];
|
||||||
this.pendingFields.set(field.target, items);
|
this.pendingFields.set(field.target, items);
|
||||||
@ -87,6 +89,18 @@ export class Database extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerModels(models: any) {
|
||||||
|
for (const [type, schemaType] of Object.entries(models)) {
|
||||||
|
this.models.set(type, schemaType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRepositories(repositories: any) {
|
||||||
|
for (const [type, schemaType] of Object.entries(repositories)) {
|
||||||
|
this.repositories.set(type, schemaType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildSchemaField(options, context) {
|
buildSchemaField(options, context) {
|
||||||
const { type } = options;
|
const { type } = options;
|
||||||
const Field = this.schemaTypes.get(type);
|
const Field = this.schemaTypes.get(type);
|
||||||
|
23
packages/collections/src/repository.ts
Normal file
23
packages/collections/src/repository.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ModelCtor, Model } from 'sequelize';
|
||||||
|
|
||||||
|
export interface IRepository {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Repository implements IRepository {
|
||||||
|
model: ModelCtor<Model>;
|
||||||
|
|
||||||
|
constructor(model: ModelCtor<Model>) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {}
|
||||||
|
|
||||||
|
findOne() {}
|
||||||
|
|
||||||
|
create() {}
|
||||||
|
|
||||||
|
update() {}
|
||||||
|
|
||||||
|
destroy() {}
|
||||||
|
}
|
@ -3,10 +3,6 @@ import { Sequelize, ModelCtor, Model, DataTypes, Utils } from 'sequelize';
|
|||||||
import { RelationField } from './relation-field';
|
import { RelationField } from './relation-field';
|
||||||
|
|
||||||
export class BelongsToManyField extends RelationField {
|
export class BelongsToManyField extends RelationField {
|
||||||
get target() {
|
|
||||||
const { target, name } = this.options;
|
|
||||||
return target || name;
|
|
||||||
}
|
|
||||||
|
|
||||||
get through() {
|
get through() {
|
||||||
return (
|
return (
|
||||||
@ -48,22 +44,10 @@ export class BelongsToManyField extends RelationField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unbind() {
|
unbind() {
|
||||||
// const { database, collection } = this.context;
|
const { database, collection } = this.context;
|
||||||
// // 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
||||||
// database.removePendingField(this);
|
database.removePendingField(this);
|
||||||
// // 如果外键没有显式的创建,关系表也无反向关联字段,删除关系时,外键也删除掉
|
// 删掉 model 的关联字段
|
||||||
// const tcoll = database.collections.get(this.target);
|
delete collection.model.associations[this.name];
|
||||||
// const foreignKey = this.options.foreignKey;
|
|
||||||
// const field1 = collection.schema.get(foreignKey);
|
|
||||||
// const field2 = tcoll.schema.find((field) => {
|
|
||||||
// return field.type === 'hasMany' && field.foreignKey === foreignKey;
|
|
||||||
// });
|
|
||||||
// if (!field1 && !field2) {
|
|
||||||
// collection.model.removeAttribute(foreignKey);
|
|
||||||
// }
|
|
||||||
// // 删掉 model 的关联字段
|
|
||||||
// delete collection.model.associations[this.name];
|
|
||||||
// // @ts-ignore
|
|
||||||
// collection.model.refreshAttributes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
packages/collections/src/schema-fields/date-field.ts
Normal file
8
packages/collections/src/schema-fields/date-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class DateField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.DATE;
|
||||||
|
}
|
||||||
|
}
|
8
packages/collections/src/schema-fields/float-field.ts
Normal file
8
packages/collections/src/schema-fields/float-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class FloatField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.FLOAT;
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,7 @@ export interface HasManyFieldOptions extends HasManyOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class HasManyField extends RelationField {
|
export class HasManyField extends RelationField {
|
||||||
|
|
||||||
bind() {
|
bind() {
|
||||||
const { database, collection } = this.context;
|
const { database, collection } = this.context;
|
||||||
const Target = this.TargetModel;
|
const Target = this.TargetModel;
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import { omit } from 'lodash';
|
||||||
|
import {
|
||||||
|
Sequelize,
|
||||||
|
ModelCtor,
|
||||||
|
Model,
|
||||||
|
DataType,
|
||||||
|
AssociationScope,
|
||||||
|
ForeignKeyOptions,
|
||||||
|
HasOneOptions,
|
||||||
|
Utils,
|
||||||
|
} from 'sequelize';
|
||||||
|
import { RelationField } from './relation-field';
|
||||||
|
|
||||||
|
export interface HasOneFieldOptions extends HasOneOptions {
|
||||||
|
/**
|
||||||
|
* The name of the field to use as the key for the association in the source table. Defaults to the primary
|
||||||
|
* key of the source table
|
||||||
|
*/
|
||||||
|
sourceKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string or a data type to represent the identifier in the table
|
||||||
|
*/
|
||||||
|
keyType?: DataType;
|
||||||
|
|
||||||
|
scope?: AssociationScope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If
|
||||||
|
* you create multiple associations between the same tables, you should provide an alias to be able to
|
||||||
|
* distinguish between them. If you provide an alias when creating the assocition, you should provide the
|
||||||
|
* same alias when eager loading and when getting associated models. Defaults to the singularized name of
|
||||||
|
* target
|
||||||
|
*/
|
||||||
|
as?: string | { singular: string; plural: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the foreign key in the target table or an object representing the type definition for the
|
||||||
|
* foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property
|
||||||
|
* to set the name of the column. Defaults to the name of source + primary key of source
|
||||||
|
*/
|
||||||
|
foreignKey?: string | ForeignKeyOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What happens when delete occurs.
|
||||||
|
*
|
||||||
|
* Cascade if this is a n:m, and set null if it is a 1:m
|
||||||
|
*
|
||||||
|
* @default 'SET NULL' or 'CASCADE'
|
||||||
|
*/
|
||||||
|
onDelete?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What happens when update occurs
|
||||||
|
*
|
||||||
|
* @default 'CASCADE'
|
||||||
|
*/
|
||||||
|
onUpdate?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should on update and on delete constraints be enabled on the foreign key.
|
||||||
|
*/
|
||||||
|
constraints?: boolean;
|
||||||
|
foreignKeyConstraint?: boolean;
|
||||||
|
|
||||||
|
// scope?: AssociationScope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `false` the applicable hooks will not be called.
|
||||||
|
* The default value depends on the context.
|
||||||
|
*/
|
||||||
|
hooks?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasOneField extends RelationField {
|
||||||
|
|
||||||
|
get target() {
|
||||||
|
const { target, name } = this.options;
|
||||||
|
return target || Utils.pluralize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
get foreignKey() {
|
||||||
|
if (this.options.foreignKey) {
|
||||||
|
return this.options.foreignKey;
|
||||||
|
}
|
||||||
|
const { model } = this.context.collection;
|
||||||
|
return Utils.camelize(
|
||||||
|
[
|
||||||
|
model.options.name.singular,
|
||||||
|
model.primaryKeyAttribute
|
||||||
|
].join('_')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() {
|
||||||
|
const { database, collection } = this.context;
|
||||||
|
const Target = this.TargetModel;
|
||||||
|
if (!Target) {
|
||||||
|
database.addPendingField(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const association = collection.model.hasOne(Target, {
|
||||||
|
as: this.name,
|
||||||
|
foreignKey: this.foreignKey,
|
||||||
|
...omit(this.options, ['name', 'type', 'target']),
|
||||||
|
});
|
||||||
|
// 建立关系之后从 pending 列表中删除
|
||||||
|
database.removePendingField(this);
|
||||||
|
if (!this.options.foreignKey) {
|
||||||
|
this.options.foreignKey = association.foreignKey;
|
||||||
|
}
|
||||||
|
if (!this.options.sourceKey) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.options.sourceKey = association.sourceKey;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind() {
|
||||||
|
const { database, collection } = this.context;
|
||||||
|
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
||||||
|
database.removePendingField(this);
|
||||||
|
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
||||||
|
const tcoll = database.collections.get(this.target);
|
||||||
|
const foreignKey = this.options.foreignKey;
|
||||||
|
const field = tcoll.schema.find((field) => {
|
||||||
|
if (field.name === foreignKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return field.type === 'belongsTo' && field.foreignKey === foreignKey;
|
||||||
|
});
|
||||||
|
if (!field) {
|
||||||
|
tcoll.model.removeAttribute(foreignKey);
|
||||||
|
}
|
||||||
|
// 删掉 model 的关联字段
|
||||||
|
delete collection.model.associations[this.name];
|
||||||
|
// @ts-ignore
|
||||||
|
collection.model.refreshAttributes();
|
||||||
|
}
|
||||||
|
}
|
@ -3,5 +3,7 @@ export * from './string-field';
|
|||||||
export * from './relation-field'
|
export * from './relation-field'
|
||||||
export * from './belongs-to-field'
|
export * from './belongs-to-field'
|
||||||
export * from './belongs-to-many-field';
|
export * from './belongs-to-many-field';
|
||||||
|
export * from './has-one-field';
|
||||||
export * from './has-many-field';
|
export * from './has-many-field';
|
||||||
export * from './json-field';
|
export * from './json-field';
|
||||||
|
export * from './sort-field';
|
||||||
|
8
packages/collections/src/schema-fields/integer-field.ts
Normal file
8
packages/collections/src/schema-fields/integer-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class IntegerField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.INTEGER;
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,6 @@ export class JsonField extends SchemaField {
|
|||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.JSON;
|
return DataTypes.JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSequelize() {
|
|
||||||
return {
|
|
||||||
...this.options,
|
|
||||||
type: this.dataType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JsonbField extends SchemaField {
|
export class JsonbField extends SchemaField {
|
||||||
@ -22,11 +15,4 @@ export class JsonbField extends SchemaField {
|
|||||||
}
|
}
|
||||||
return DataTypes.JSON;
|
return DataTypes.JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSequelize() {
|
|
||||||
return {
|
|
||||||
...this.options,
|
|
||||||
type: this.dataType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
32
packages/collections/src/schema-fields/number-field.ts
Normal file
32
packages/collections/src/schema-fields/number-field.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class IntegerField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.INTEGER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FloatField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.FLOAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DoubleField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.DOUBLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RealField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.REAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DecimalField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.DECIMAL;
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,10 @@ export abstract class SchemaField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toSequelize(): any {
|
toSequelize(): any {
|
||||||
return _.omit(this.options, ['name'])
|
const opts = _.omit(this.options, ['name']);
|
||||||
|
if (this.dataType) {
|
||||||
|
Object.assign(opts, { type: this.dataType });
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
packages/collections/src/schema-fields/sort-field.ts
Normal file
25
packages/collections/src/schema-fields/sort-field.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { isNumber } from 'lodash';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class SortField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const { name, scopeKey } = this.options;
|
||||||
|
const { model } = this.context.collection;
|
||||||
|
model.beforeCreate(async (instance, options) => {
|
||||||
|
if (isNumber(instance.get(name))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const where = {};
|
||||||
|
if (scopeKey) {
|
||||||
|
where[scopeKey] = instance.get(scopeKey);
|
||||||
|
}
|
||||||
|
const max = await model.max<number, any>(name, { ...options, where });
|
||||||
|
instance.set(name, (max || 0) + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,4 @@ export class StringField extends SchemaField {
|
|||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.STRING;
|
return DataTypes.STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSequelize() {
|
|
||||||
return {
|
|
||||||
...this.options,
|
|
||||||
type: this.dataType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
8
packages/collections/src/schema-fields/text-field.ts
Normal file
8
packages/collections/src/schema-fields/text-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class TextField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.TEXT;
|
||||||
|
}
|
||||||
|
}
|
8
packages/collections/src/schema-fields/time-field.ts
Normal file
8
packages/collections/src/schema-fields/time-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class TimeField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.TIME;
|
||||||
|
}
|
||||||
|
}
|
8
packages/collections/src/schema-fields/virtual-field.ts
Normal file
8
packages/collections/src/schema-fields/virtual-field.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { SchemaField } from './schema-field';
|
||||||
|
|
||||||
|
export class VirtualField extends SchemaField {
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.VIRTUAL;
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ export class Schema extends EventEmitter {
|
|||||||
schema: this,
|
schema: this,
|
||||||
model: this.context.collection.model,
|
model: this.context.collection.model,
|
||||||
});
|
});
|
||||||
|
// console.log('field', field);
|
||||||
this.fields.set(name, field);
|
this.fields.set(name, field);
|
||||||
this.emit('setted', field);
|
this.emit('setted', field);
|
||||||
} else if (Array.isArray(name)) {
|
} else if (Array.isArray(name)) {
|
||||||
@ -59,7 +60,9 @@ export class Schema extends EventEmitter {
|
|||||||
delete(name: string) {
|
delete(name: string) {
|
||||||
const field = this.fields.get(name);
|
const field = this.fields.get(name);
|
||||||
const bool = this.fields.delete(name);
|
const bool = this.fields.delete(name);
|
||||||
this.emit('deleted', field);
|
if (bool) {
|
||||||
|
this.emit('deleted', field);
|
||||||
|
}
|
||||||
return bool;
|
return bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
192
packages/collections/src/update-associations.ts
Normal file
192
packages/collections/src/update-associations.ts
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import {
|
||||||
|
Sequelize,
|
||||||
|
ModelCtor,
|
||||||
|
Model,
|
||||||
|
DataTypes,
|
||||||
|
Utils,
|
||||||
|
Association,
|
||||||
|
} from 'sequelize';
|
||||||
|
|
||||||
|
function isUndefinedOrNull(value: any) {
|
||||||
|
return typeof value === 'undefined' || value === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStringOrNumber(value: any) {
|
||||||
|
return typeof value === 'string' || typeof value === 'number';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAssociations(
|
||||||
|
model: Model,
|
||||||
|
values: any,
|
||||||
|
options: any = {},
|
||||||
|
) {
|
||||||
|
const { transaction = await model.sequelize.transaction() } = options;
|
||||||
|
// @ts-ignore
|
||||||
|
for (const key of Object.keys(model.constructor.associations)) {
|
||||||
|
// 如果 key 不存在才跳过
|
||||||
|
if (!Object.keys(values).includes(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await updateAssociation(model, key, values[key], {
|
||||||
|
...options,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAssociation(
|
||||||
|
model: Model,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
options: any = {},
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
|
const association = model.constructor.associations[key] as Association;
|
||||||
|
if (!association) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (association.associationType) {
|
||||||
|
case 'HasOne':
|
||||||
|
case 'BelongsTo':
|
||||||
|
return updateSingleAssociation(model, key, value, options);
|
||||||
|
case 'HasMany':
|
||||||
|
case 'BelongsToMany':
|
||||||
|
return updateMultipleAssociation(model, key, value, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSingleAssociation(
|
||||||
|
model: Model,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
options: any = {},
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
|
const association = model.constructor.associations[key] as Association;
|
||||||
|
if (!association) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { transaction = await model.sequelize.transaction() } = options;
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const setAccessor = association.accessors.set;
|
||||||
|
if (isUndefinedOrNull(value)) {
|
||||||
|
return await model[setAccessor](null, { transaction });
|
||||||
|
}
|
||||||
|
if (isStringOrNumber(value)) {
|
||||||
|
return await model[setAccessor](value, { transaction });
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const createAccessor = association.accessors.create;
|
||||||
|
let key: string;
|
||||||
|
let M: ModelCtor<Model>;
|
||||||
|
if (association.associationType === 'BelongsTo') {
|
||||||
|
// @ts-ignore
|
||||||
|
key = association.targetKey;
|
||||||
|
M = association.target;
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
key = association.sourceKey;
|
||||||
|
M = association.source;
|
||||||
|
}
|
||||||
|
if (isStringOrNumber(value)) {
|
||||||
|
let instance: any = await M.findOne({
|
||||||
|
where: {
|
||||||
|
[key]: value[key],
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
if (!instance) {
|
||||||
|
instance = await M.create(value, { transaction });
|
||||||
|
}
|
||||||
|
await model[setAccessor](value[key]);
|
||||||
|
await updateAssociations(instance, value, { transaction, ...options });
|
||||||
|
} else {
|
||||||
|
const instance = await model[createAccessor](value, { transaction });
|
||||||
|
await updateAssociations(instance, value, { transaction, ...options });
|
||||||
|
}
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.rollback();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateMultipleAssociation(
|
||||||
|
model: Model,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
options: any = {},
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
|
const association = model.constructor.associations[key] as Association;
|
||||||
|
if (!association) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { transaction = await model.sequelize.transaction() } = options;
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const setAccessor = association.accessors.set;
|
||||||
|
// @ts-ignore
|
||||||
|
const createAccessor = association.accessors.create;
|
||||||
|
if (isUndefinedOrNull(value)) {
|
||||||
|
return await model[setAccessor](null, { transaction });
|
||||||
|
}
|
||||||
|
if (isStringOrNumber(value)) {
|
||||||
|
return await model[setAccessor](value, { transaction });
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
value = [value];
|
||||||
|
}
|
||||||
|
const list1 = []; // to be setted
|
||||||
|
const list2 = []; // to be added
|
||||||
|
for (const item of value) {
|
||||||
|
if (isUndefinedOrNull(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isStringOrNumber(item)) {
|
||||||
|
list1.push(item);
|
||||||
|
} else if (item instanceof Model) {
|
||||||
|
list1.push(item);
|
||||||
|
} else if (item.sequelize) {
|
||||||
|
list1.push(item);
|
||||||
|
} else if (typeof item === 'object') {
|
||||||
|
list2.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('updateMultipleAssociation', list1, list2);
|
||||||
|
await model[setAccessor](list1, { transaction });
|
||||||
|
for (const item of list2) {
|
||||||
|
const pk = association.target.primaryKeyAttribute;
|
||||||
|
if (isUndefinedOrNull(item[pk])) {
|
||||||
|
const instance = await model[createAccessor](item, { transaction });
|
||||||
|
await updateAssociations(instance, item, { transaction, ...options });
|
||||||
|
} else {
|
||||||
|
const instance = await association.target.findByPk(item[pk], { transaction });
|
||||||
|
// @ts-ignore
|
||||||
|
const addAccessor = association.accessors.add;
|
||||||
|
await model[addAccessor](item[pk], { transaction });
|
||||||
|
await updateAssociations(instance, item, { transaction, ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
18
packages/collections/src/utils.ts
Normal file
18
packages/collections/src/utils.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
fiter: {
|
||||||
|
and: [
|
||||||
|
{ a: 'a' },
|
||||||
|
{ b: 'b' },
|
||||||
|
{ c: 'c' },
|
||||||
|
{ 'assoc.a': 'abc1' },
|
||||||
|
{ 'assoc.b': 'abc2' },
|
||||||
|
{ 'assoc.c': 'abc3' },
|
||||||
|
{
|
||||||
|
and: [
|
||||||
|
{ 'assoc.a': 'abc1' },
|
||||||
|
{ 'assoc.b': 'abc2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
@ -1,13 +1,16 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Application from '@nocobase/server';
|
import { IPlugin } from '@nocobase/server';
|
||||||
import { afterCreate, afterUpdate, afterDestroy } from './hooks';
|
import { afterCreate, afterUpdate, afterDestroy } from './hooks';
|
||||||
|
|
||||||
export default async function (this: Application) {
|
export default {
|
||||||
const { database } = this;
|
name: 'action-logs',
|
||||||
database.import({
|
async load() {
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
const database = this.app.db;
|
||||||
});
|
database.import({
|
||||||
database.on('afterCreate', afterCreate);
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
database.on('afterUpdate', afterUpdate);
|
});
|
||||||
database.on('afterDestroy', afterDestroy);
|
database.on('afterCreate', afterCreate);
|
||||||
}
|
database.on('afterUpdate', afterUpdate);
|
||||||
|
database.on('afterDestroy', afterDestroy);
|
||||||
|
}
|
||||||
|
} as IPlugin;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
import Database, { registerModels } from '@nocobase/database';
|
import Database, { registerModels } from '@nocobase/database';
|
||||||
import Resourcer from '@nocobase/resourcer';
|
import Resourcer from '@nocobase/resourcer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Database, { registerModels } from '@nocobase/database';
|
import { registerModels } from '@nocobase/database';
|
||||||
import { ChinaRegion } from './models/china-region';
|
import { ChinaRegion } from './models/china-region';
|
||||||
import Application from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
registerModels({ ChinaRegion });
|
registerModels({ ChinaRegion });
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default {
|
||||||
const { db } = this;
|
name: 'china-region',
|
||||||
|
async load(this: Plugin) {
|
||||||
db.import({
|
const db = this.app.db;
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
|
||||||
});
|
db.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
this.on('db.init', async () => {
|
});
|
||||||
const M = db.getModel('china_regions');
|
|
||||||
await M.importData();
|
this.app.on('db.init', async () => {
|
||||||
});
|
const M = db.getModel('china_regions');
|
||||||
}
|
await M.importData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,123 +1,132 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Application } from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
import { registerModels, Table, uid } from '@nocobase/database';
|
import { registerModels, Table, uid } from '@nocobase/database';
|
||||||
import * as models from './models';
|
import * as models from './models';
|
||||||
import { createOrUpdate, findAll } from './actions';
|
import { createOrUpdate, findAll } from './actions';
|
||||||
import { create } from './actions/fields';
|
import { create } from './actions/fields';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default {
|
||||||
const database = this.db;
|
name: 'collections',
|
||||||
|
async load(this: Plugin) {
|
||||||
|
const database = this.app.db;
|
||||||
|
|
||||||
registerModels(models);
|
registerModels(models);
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
|
||||||
|
|
||||||
this.on('afterLoadPlugins', async () => {
|
|
||||||
await database.getModel('collections').load();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('db.init', async () => {
|
|
||||||
const userTable = database.getTable('users');
|
|
||||||
const config = userTable.getOptions();
|
|
||||||
const Collection = database.getModel('collections');
|
|
||||||
const collection = await Collection.create(config);
|
|
||||||
await collection.updateAssociations({
|
|
||||||
generalFields: config.fields.filter((field) => field.state !== 0),
|
|
||||||
systemFields: config.fields.filter((field) => field.state === 0),
|
|
||||||
});
|
});
|
||||||
await collection.migrate();
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Collection, Field] = database.getModels(['collections', 'fields']);
|
this.app.on('beforeStart', async () => {
|
||||||
|
await database.getModel('collections').load();
|
||||||
|
});
|
||||||
|
|
||||||
database.on('fields.beforeCreate', async (model) => {
|
this.app.on('db.init', async () => {
|
||||||
if (!model.get('name')) {
|
const userTable = database.getTable('users');
|
||||||
model.set('name', model.get('key'));
|
const config = userTable.getOptions();
|
||||||
}
|
const Collection = database.getModel('collections');
|
||||||
if (!model.get('collection_name') && model.get('parentKey')) {
|
const collection = await Collection.create(config);
|
||||||
const field = await Field.findByPk(model.get('parentKey'));
|
await collection.updateAssociations({
|
||||||
if (field) {
|
generalFields: config.fields.filter((field) => field.state !== 0),
|
||||||
const { target } = field.get('options') || {};
|
systemFields: config.fields.filter((field) => field.state === 0),
|
||||||
if (target) {
|
});
|
||||||
model.set('collection_name', target);
|
await collection.migrate();
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Collection, Field] = database.getModels(['collections', 'fields']);
|
||||||
|
|
||||||
|
database.on('fields.beforeCreate', async (model) => {
|
||||||
|
if (!model.get('name')) {
|
||||||
|
model.set('name', model.get('key'));
|
||||||
|
}
|
||||||
|
if (!model.get('collection_name') && model.get('parentKey')) {
|
||||||
|
const field = await Field.findByPk(model.get('parentKey'));
|
||||||
|
if (field) {
|
||||||
|
const { target } = field.get('options') || {};
|
||||||
|
if (target) {
|
||||||
|
model.set('collection_name', target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
database.on('fields.beforeUpdate', async (model) => {
|
database.on('fields.beforeUpdate', async (model) => {
|
||||||
console.log('beforeUpdate', model.key);
|
console.log('beforeUpdate', model.key);
|
||||||
if (!model.get('collection_name') && model.get('parentKey')) {
|
if (!model.get('collection_name') && model.get('parentKey')) {
|
||||||
const field = await Field.findByPk(model.get('parentKey'));
|
const field = await Field.findByPk(model.get('parentKey'));
|
||||||
if (field) {
|
if (field) {
|
||||||
const { target } = field.get('options') || {};
|
const { target } = field.get('options') || {};
|
||||||
if (target) {
|
if (target) {
|
||||||
model.set('collection_name', target);
|
model.set('collection_name', target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
database.on('fields.afterCreate', async (model) => {
|
database.on('fields.afterCreate', async (model) => {
|
||||||
console.log('afterCreate', model.key, model.get('collection_name'));
|
console.log('afterCreate', model.key, model.get('collection_name'));
|
||||||
if (model.get('interface') !== 'subTable') {
|
if (model.get('interface') !== 'subTable') {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
const { target } = model.get('options') || {};
|
|
||||||
// const uiSchemaKey = model.get('ui_schema_key');
|
|
||||||
// console.log({ uiSchemaKey })
|
|
||||||
try {
|
|
||||||
let collection = await Collection.findOne({
|
|
||||||
where: {
|
|
||||||
name: target,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!collection) {
|
|
||||||
collection = await Collection.create({
|
|
||||||
name: target,
|
|
||||||
// ui_schema_key: uiSchemaKey,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// if (model.get('ui_schema_key')) {
|
const { target } = model.get('options') || {};
|
||||||
// collection.set('ui_schema_key', model.get('ui_schema_key'));
|
// const uiSchemaKey = model.get('ui_schema_key');
|
||||||
// await collection.save({ hooks: false });
|
// console.log({ uiSchemaKey })
|
||||||
// }
|
try {
|
||||||
await collection.migrate();
|
let collection = await Collection.findOne({
|
||||||
} catch (error) {
|
where: {
|
||||||
throw error;
|
name: target,
|
||||||
}
|
},
|
||||||
});
|
|
||||||
|
|
||||||
database.on('fields.afterUpdate', async (model) => {
|
|
||||||
console.log('afterUpdate');
|
|
||||||
if (model.get('interface') !== 'subTable') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { target } = model.get('options') || {};
|
|
||||||
try {
|
|
||||||
let collection = await Collection.findOne({
|
|
||||||
where: {
|
|
||||||
name: target,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!collection) {
|
|
||||||
collection = await Collection.create({
|
|
||||||
name: target,
|
|
||||||
});
|
});
|
||||||
|
if (!collection) {
|
||||||
|
collection = await Collection.create({
|
||||||
|
name: target,
|
||||||
|
// ui_schema_key: uiSchemaKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if (model.get('ui_schema_key')) {
|
||||||
|
// collection.set('ui_schema_key', model.get('ui_schema_key'));
|
||||||
|
// await collection.save({ hooks: false });
|
||||||
|
// }
|
||||||
|
await collection.migrate();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
// if (model.get('ui_schema_key')) {
|
});
|
||||||
// collection.set('ui_schema_key', model.get('ui_schema_key'));
|
|
||||||
// await collection.save({ hooks: false });
|
|
||||||
// }
|
|
||||||
await collection.migrate();
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.resourcer.registerActionHandler('collections.fields:create', create);
|
database.on('fields.afterUpdate', async (model) => {
|
||||||
this.resourcer.registerActionHandler('collections:findAll', findAll);
|
console.log('afterUpdate');
|
||||||
this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate);
|
if (model.get('interface') !== 'subTable') {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
const { target } = model.get('options') || {};
|
||||||
|
try {
|
||||||
|
let collection = await Collection.findOne({
|
||||||
|
where: {
|
||||||
|
name: target,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!collection) {
|
||||||
|
collection = await Collection.create({
|
||||||
|
name: target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if (model.get('ui_schema_key')) {
|
||||||
|
// collection.set('ui_schema_key', model.get('ui_schema_key'));
|
||||||
|
// await collection.save({ hooks: false });
|
||||||
|
// }
|
||||||
|
await collection.migrate();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.resourcer.registerActionHandler(
|
||||||
|
'collections.fields:create',
|
||||||
|
create,
|
||||||
|
);
|
||||||
|
this.app.resourcer.registerActionHandler('collections:findAll', findAll);
|
||||||
|
this.app.resourcer.registerActionHandler(
|
||||||
|
'collections:createOrUpdate',
|
||||||
|
createOrUpdate,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
import Resourcer from '@nocobase/resourcer';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
import _export from './actions/export';
|
import _export from './actions/export';
|
||||||
|
|
||||||
export const ACTION_NAME_EXPORT = 'export';
|
export const ACTION_NAME_EXPORT = 'export';
|
||||||
|
|
||||||
export default async function (options = {}) {
|
export default {
|
||||||
const resourcer: Resourcer = this.resourcer;
|
name: 'export',
|
||||||
|
async load() {
|
||||||
resourcer.registerActionHandler(ACTION_NAME_EXPORT, _export);
|
const resourcer = this.app.resourcer;
|
||||||
|
resourcer.registerActionHandler(ACTION_NAME_EXPORT, _export);
|
||||||
// // TODO(temp): 继承 list 权限的临时写法
|
// // TODO(temp): 继承 list 权限的临时写法
|
||||||
// resourcer.use(async (ctx, next) => {
|
// resourcer.use(async (ctx, next) => {
|
||||||
// if (ctx.action.params.actionName === ACTION_NAME_EXPORT) {
|
// if (ctx.action.params.actionName === ACTION_NAME_EXPORT) {
|
||||||
// ctx.action.mergeParams({
|
// ctx.action.mergeParams({
|
||||||
// actionName: 'list'
|
// actionName: 'list'
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// console.log('action name in export has been rewritten to:', ctx.action.params.actionName);
|
// console.log('action name in export has been rewritten to:', ctx.action.params.actionName);
|
||||||
|
|
||||||
// const permissionPlugin = ctx.app.getPluginInstance('@nocobase/plugin-permissions');
|
// const permissionPlugin = ctx.app.getPluginInstance('@nocobase/plugin-permissions');
|
||||||
// if (permissionPlugin) {
|
// if (permissionPlugin) {
|
||||||
// return permissionPlugin.middleware(ctx, next);
|
// return permissionPlugin.middleware(ctx, next);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// await next();
|
// await next();
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
} as PluginOptions;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Database from '@nocobase/database';
|
import Database from '@nocobase/database';
|
||||||
import Resourcer from '@nocobase/resourcer';
|
import Resourcer from '@nocobase/resourcer';
|
||||||
|
import { PluginOptions, Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
action as uploadAction,
|
action as uploadAction,
|
||||||
@ -10,40 +11,43 @@ import {
|
|||||||
middleware as localMiddleware,
|
middleware as localMiddleware,
|
||||||
} from './storages/local';
|
} from './storages/local';
|
||||||
|
|
||||||
export default async function () {
|
export default {
|
||||||
const database: Database = this.database;
|
name: 'file-manager',
|
||||||
const resourcer: Resourcer = this.resourcer;
|
async load() {
|
||||||
|
const database: Database = this.app.db;
|
||||||
database.import({
|
const resourcer: Resourcer = this.app.resourcer;
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
|
||||||
});
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
// 暂时中间件只能通过 use 加进来
|
|
||||||
resourcer.use(uploadMiddleware);
|
|
||||||
resourcer.registerActionHandler('upload', uploadAction);
|
|
||||||
localMiddleware(this);
|
|
||||||
|
|
||||||
const Storage = database.getModel('storages');
|
|
||||||
|
|
||||||
this.on('db.init', async () => {
|
|
||||||
await Storage.create({
|
|
||||||
title: '本地存储',
|
|
||||||
name: `local`,
|
|
||||||
type: 'local',
|
|
||||||
baseUrl: process.env.LOCAL_STORAGE_BASE_URL,
|
|
||||||
default: process.env.STORAGE_TYPE === 'local',
|
|
||||||
});
|
});
|
||||||
await Storage.create({
|
|
||||||
name: `ali-oss`,
|
// 暂时中间件只能通过 use 加进来
|
||||||
type: 'ali-oss',
|
resourcer.use(uploadMiddleware);
|
||||||
baseUrl: process.env.ALI_OSS_STORAGE_BASE_URL,
|
resourcer.registerActionHandler('upload', uploadAction);
|
||||||
options: {
|
localMiddleware(this.app);
|
||||||
region: process.env.ALI_OSS_REGION,
|
|
||||||
accessKeyId: process.env.ALI_OSS_ACCESS_KEY_ID,
|
const Storage = database.getModel('storages');
|
||||||
accessKeySecret: process.env.ALI_OSS_ACCESS_KEY_SECRET,
|
|
||||||
bucket: process.env.ALI_OSS_BUCKET,
|
this.app.on('db.init', async () => {
|
||||||
},
|
await Storage.create({
|
||||||
default: process.env.STORAGE_TYPE === 'ali-oss',
|
title: '本地存储',
|
||||||
|
name: `local`,
|
||||||
|
type: 'local',
|
||||||
|
baseUrl: process.env.LOCAL_STORAGE_BASE_URL,
|
||||||
|
default: process.env.STORAGE_TYPE === 'local',
|
||||||
|
});
|
||||||
|
await Storage.create({
|
||||||
|
name: `ali-oss`,
|
||||||
|
type: 'ali-oss',
|
||||||
|
baseUrl: process.env.ALI_OSS_STORAGE_BASE_URL,
|
||||||
|
options: {
|
||||||
|
region: process.env.ALI_OSS_REGION,
|
||||||
|
accessKeyId: process.env.ALI_OSS_ACCESS_KEY_ID,
|
||||||
|
accessKeySecret: process.env.ALI_OSS_ACCESS_KEY_SECRET,
|
||||||
|
bucket: process.env.ALI_OSS_BUCKET,
|
||||||
|
},
|
||||||
|
default: process.env.STORAGE_TYPE === 'ali-oss',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
}
|
} as PluginOptions;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Database from '@nocobase/database';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
import Resourcer from '@nocobase/resourcer';
|
|
||||||
|
|
||||||
export default async function () {
|
export default {
|
||||||
const database: Database = this.database;
|
name: 'permissions',
|
||||||
const resourcer: Resourcer = this.resourcer;
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} as PluginOptions;
|
||||||
|
@ -1,42 +1,45 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Application } from '@nocobase/server';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default {
|
||||||
const database = this.database;
|
name: 'system-settings',
|
||||||
const resourcer = this.resourcer;
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
|
const resourcer = this.app.resourcer;
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const SystemSetting = database.getModel('system_settings');
|
const SystemSetting = database.getModel('system_settings');
|
||||||
|
|
||||||
resourcer.use(async (ctx, next) => {
|
resourcer.use(async (ctx, next) => {
|
||||||
const { actionName, resourceName, resourceKey } = ctx.action.params;
|
const { actionName, resourceName, resourceKey } = ctx.action.params;
|
||||||
if (resourceName === 'system_settings' && actionName === 'get') {
|
if (resourceName === 'system_settings' && actionName === 'get') {
|
||||||
let model = await SystemSetting.findOne();
|
let model = await SystemSetting.findOne();
|
||||||
if (!model) {
|
if (!model) {
|
||||||
model = await SystemSetting.create();
|
model = await SystemSetting.create();
|
||||||
|
}
|
||||||
|
ctx.action.mergeParams({
|
||||||
|
resourceKey: model.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ctx.action.mergeParams({
|
await next();
|
||||||
resourceKey: model.id,
|
});
|
||||||
|
|
||||||
|
this.app.on('db.init', async () => {
|
||||||
|
const setting = await SystemSetting.create({
|
||||||
|
title: 'NocoBase',
|
||||||
|
});
|
||||||
|
await setting.updateAssociations({
|
||||||
|
logo: {
|
||||||
|
title: 'nocobase-logo',
|
||||||
|
filename: '682e5ad037dd02a0fe4800a3e91c283b.png',
|
||||||
|
extname: '.png',
|
||||||
|
mimetype: 'image/png',
|
||||||
|
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('db.init', async () => {
|
|
||||||
const setting = await SystemSetting.create({
|
|
||||||
title: 'NocoBase',
|
|
||||||
});
|
});
|
||||||
await setting.updateAssociations({
|
}
|
||||||
logo: {
|
} as PluginOptions;
|
||||||
title: 'nocobase-logo',
|
|
||||||
filename: '682e5ad037dd02a0fe4800a3e91c283b.png',
|
|
||||||
extname: '.png',
|
|
||||||
mimetype: 'image/png',
|
|
||||||
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -1,61 +1,64 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Application } from '@nocobase/server';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
import { registerModels } from '@nocobase/database';
|
import { registerModels } from '@nocobase/database';
|
||||||
import * as models from './models';
|
import * as models from './models';
|
||||||
import getAccessible from './actions/getAccessible';
|
import getAccessible from './actions/getAccessible';
|
||||||
import * as uiSchema from './ui-schema';
|
import * as uiSchema from './ui-schema';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default {
|
||||||
const database = this.database;
|
name: 'ui-router',
|
||||||
registerModels(models);
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
|
registerModels(models);
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resourcer.registerActionHandler('routes:getAccessible', getAccessible);
|
this.app.resourcer.registerActionHandler('routes:getAccessible', getAccessible);
|
||||||
|
|
||||||
const Route = database.getModel('routes');
|
const Route = database.getModel('routes');
|
||||||
|
|
||||||
this.on('db.init', async () => {
|
this.app.on('db.init', async () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
type: 'redirect',
|
type: 'redirect',
|
||||||
from: '/',
|
from: '/',
|
||||||
to: '/admin',
|
to: '/admin',
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'route',
|
type: 'route',
|
||||||
path: '/admin/:name(.+)?',
|
path: '/admin/:name(.+)?',
|
||||||
component: 'AdminLayout',
|
component: 'AdminLayout',
|
||||||
title: `后台`,
|
title: `后台`,
|
||||||
uiSchema: uiSchema.menu,
|
uiSchema: uiSchema.menu,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'route',
|
type: 'route',
|
||||||
component: 'AuthLayout',
|
component: 'AuthLayout',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'route',
|
type: 'route',
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: 'RouteSchemaRenderer',
|
component: 'RouteSchemaRenderer',
|
||||||
title: `登录`,
|
title: `登录`,
|
||||||
uiSchema: uiSchema.login,
|
uiSchema: uiSchema.login,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'route',
|
type: 'route',
|
||||||
path: '/register',
|
path: '/register',
|
||||||
component: 'RouteSchemaRenderer',
|
component: 'RouteSchemaRenderer',
|
||||||
title: `注册`,
|
title: `注册`,
|
||||||
uiSchema: uiSchema.register,
|
uiSchema: uiSchema.register,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
const route = await Route.create(item);
|
const route = await Route.create(item);
|
||||||
await route.updateAssociations(item);
|
await route.updateAssociations(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} as PluginOptions;
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Application } from '@nocobase/server';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
import { registerModels } from '@nocobase/database';
|
import { registerModels } from '@nocobase/database';
|
||||||
import * as models from './models';
|
import * as models from './models';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
registerModels(models);
|
||||||
const database = this.database;
|
|
||||||
registerModels(models);
|
|
||||||
|
|
||||||
database.import({
|
export default {
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
name: 'ui-schema',
|
||||||
});
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
for (const [name, action] of Object.entries(actions)) {
|
|
||||||
this.resourcer.registerActionHandler(`ui_schemas:${name}`, action);
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [name, action] of Object.entries(actions)) {
|
||||||
|
this.app.resourcer.registerActionHandler(`ui_schemas:${name}`, action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} as PluginOptions;
|
||||||
|
@ -1,51 +1,53 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Database, { registerFields, Table } from '@nocobase/database';
|
import { registerFields, Table } from '@nocobase/database';
|
||||||
import Resourcer from '@nocobase/resourcer';
|
|
||||||
import * as fields from './fields';
|
import * as fields from './fields';
|
||||||
import * as usersActions from './actions/users';
|
import * as usersActions from './actions/users';
|
||||||
import * as middlewares from './middlewares';
|
import * as middlewares from './middlewares';
|
||||||
import Application from '@nocobase/server';
|
import { PluginOptions } from '@nocobase/server';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default {
|
||||||
const database: Database = this.database;
|
name: 'users',
|
||||||
const resourcer: Resourcer = this.resourcer;
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
|
const resourcer = this.app.resourcer;
|
||||||
|
|
||||||
registerFields(fields);
|
registerFields(fields);
|
||||||
|
|
||||||
this.on('db.init', async () => {
|
this.app.on('db.init', async () => {
|
||||||
const User = database.getModel('users');
|
const User = database.getModel('users');
|
||||||
await User.create({
|
await User.create({
|
||||||
nickname: '超级管理员',
|
nickname: '超级管理员',
|
||||||
email: process.env.ADMIN_EMAIL,
|
email: process.env.ADMIN_EMAIL,
|
||||||
password: process.env.ADMIN_PASSWORD,
|
password: process.env.ADMIN_PASSWORD,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
database.on('afterTableInit', (table: Table) => {
|
database.on('afterTableInit', (table: Table) => {
|
||||||
let { createdBy, updatedBy } = table.getOptions();
|
let { createdBy, updatedBy } = table.getOptions();
|
||||||
if (createdBy !== false) {
|
if (createdBy !== false) {
|
||||||
table.addField({
|
table.addField({
|
||||||
type: 'createdBy',
|
type: 'createdBy',
|
||||||
name: typeof createdBy === 'string' ? createdBy : 'createdBy',
|
name: typeof createdBy === 'string' ? createdBy : 'createdBy',
|
||||||
target: 'users',
|
target: 'users',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (updatedBy !== false) {
|
||||||
|
table.addField({
|
||||||
|
type: 'updatedBy',
|
||||||
|
name: typeof updatedBy === 'string' ? updatedBy : 'updatedBy',
|
||||||
|
target: 'users',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [key, action] of Object.entries(usersActions)) {
|
||||||
|
resourcer.registerActionHandler(`users:${key}`, action);
|
||||||
}
|
}
|
||||||
if (updatedBy !== false) {
|
|
||||||
table.addField({
|
|
||||||
type: 'updatedBy',
|
|
||||||
name: typeof updatedBy === 'string' ? updatedBy : 'updatedBy',
|
|
||||||
target: 'users',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
database.import({
|
resourcer.use(middlewares.parseToken({}));
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
},
|
||||||
});
|
} as PluginOptions;
|
||||||
|
|
||||||
for (const [key, action] of Object.entries(usersActions)) {
|
|
||||||
resourcer.registerActionHandler(`users:${key}`, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
resourcer.use(middlewares.parseToken(options));
|
|
||||||
}
|
|
||||||
|
@ -38,20 +38,25 @@ describe('plugin', () => {
|
|||||||
expect(plugin).toBeInstanceOf(Plugin);
|
expect(plugin).toBeInstanceOf(Plugin);
|
||||||
expect(plugin.getName()).toBe('abc');
|
expect(plugin.getName()).toBe('abc');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('plugin name', async () => {
|
it('plugin name', async () => {
|
||||||
const plugin = app.plugin(function abc() {}, {
|
const plugin = app.plugin({
|
||||||
name: 'plugin-name2'
|
name: 'plugin-name2',
|
||||||
|
async load() {},
|
||||||
});
|
});
|
||||||
expect(plugin).toBeInstanceOf(Plugin);
|
expect(plugin).toBeInstanceOf(Plugin);
|
||||||
expect(plugin.getName()).toBe('plugin-name2');
|
expect(plugin.getName()).toBe('plugin-name2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('plugin name', async () => {
|
it('plugin name', async () => {
|
||||||
const plugin = app.plugin(function () {}, {
|
const plugin = app.plugin({
|
||||||
name: 'plugin-name3'
|
name: 'plugin-name3',
|
||||||
|
load: function () {},
|
||||||
});
|
});
|
||||||
expect(plugin).toBeInstanceOf(Plugin);
|
expect(plugin).toBeInstanceOf(Plugin);
|
||||||
expect(plugin.getName()).toBe('plugin-name3');
|
expect(plugin.getName()).toBe('plugin-name3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('plugin name', async () => {
|
it('plugin name', async () => {
|
||||||
class MyPlugin extends Plugin {}
|
class MyPlugin extends Plugin {}
|
||||||
const plugin = app.plugin(MyPlugin);
|
const plugin = app.plugin(MyPlugin);
|
||||||
@ -59,6 +64,15 @@ describe('plugin', () => {
|
|||||||
expect(plugin.getName()).toBe('MyPlugin');
|
expect(plugin.getName()).toBe('MyPlugin');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('plugin name', async () => {
|
||||||
|
class MyPlugin extends Plugin {}
|
||||||
|
const plugin = app.plugin({
|
||||||
|
plugin: MyPlugin,
|
||||||
|
});
|
||||||
|
expect(plugin).toBeInstanceOf(MyPlugin);
|
||||||
|
expect(plugin.getName()).toBe('MyPlugin');
|
||||||
|
});
|
||||||
|
|
||||||
it('plugin name', async () => {
|
it('plugin name', async () => {
|
||||||
const plugin = app.plugin(path.resolve(__dirname, './plugins/plugin1'));
|
const plugin = app.plugin(path.resolve(__dirname, './plugins/plugin1'));
|
||||||
expect(plugin).toBeInstanceOf(Plugin);
|
expect(plugin).toBeInstanceOf(Plugin);
|
||||||
|
@ -142,17 +142,16 @@ export class Application<
|
|||||||
console.log(args);
|
console.log(args);
|
||||||
const opts = cli.opts();
|
const opts = cli.opts();
|
||||||
await this.load();
|
await this.load();
|
||||||
await this.emitAsync('server.beforeStart');
|
await this.emitAsync('beforeStart');
|
||||||
this.listen(opts.port || 3000);
|
this.listen(opts.port || 3000);
|
||||||
console.log(`http://localhost:${opts.port || 3000}/`);
|
console.log(`http://localhost:${opts.port || 3000}/`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
use<NewStateT = {}, NewContextT = {}>(
|
use<NewStateT = {}, NewContextT = {}>(
|
||||||
middleware: Koa.Middleware<StateT & NewStateT, ContextT & NewContextT>,
|
middleware: Koa.Middleware<StateT & NewStateT, ContextT & NewContextT>,
|
||||||
options?: MiddlewareOptions,
|
options?: MiddlewareOptions,
|
||||||
): Application<StateT & NewStateT, ContextT & NewContextT> {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return super.use(middleware);
|
return super.use(middleware);
|
||||||
}
|
}
|
||||||
@ -173,25 +172,34 @@ export class Application<
|
|||||||
return this.cli.command(nameAndArgs, opts);
|
return this.cli.command(nameAndArgs, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin(plugin: PluginType, options?: PluginOptions): Plugin {
|
plugin(options?: PluginType | PluginOptions): Plugin {
|
||||||
if (typeof plugin === 'string') {
|
if (typeof options === 'string') {
|
||||||
plugin = require(plugin).default;
|
return this.plugin(require(options).default);
|
||||||
}
|
}
|
||||||
let instance: Plugin;
|
let instance: Plugin;
|
||||||
try {
|
if (typeof options === 'function') {
|
||||||
// @ts-ignore
|
try {
|
||||||
const p = new plugin();
|
|
||||||
if (p instanceof Plugin) {
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
instance = new plugin({}, {
|
instance = new options({
|
||||||
...options,
|
name: options.name,
|
||||||
|
app: this,
|
||||||
|
});
|
||||||
|
if (!(instance instanceof Plugin)) {
|
||||||
|
throw new Error('plugin must be instanceof Plugin');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err);
|
||||||
|
instance = new Plugin({
|
||||||
|
name: options.name,
|
||||||
|
// @ts-ignore
|
||||||
|
load: options,
|
||||||
app: this,
|
app: this,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
throw new Error('plugin must be instanceof Plugin');
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} else if (typeof options === 'object') {
|
||||||
instance = new Plugin(plugin, {
|
const plugin = options.plugin || Plugin;
|
||||||
|
instance = new plugin({
|
||||||
|
name: options.plugin ? plugin.name : undefined,
|
||||||
...options,
|
...options,
|
||||||
app: this,
|
app: this,
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './application';
|
export * from './application';
|
||||||
export * as middlewares from './middlewares';
|
export * as middlewares from './middlewares';
|
||||||
|
export * from './plugin';
|
||||||
export { Application as default } from './application';
|
export { Application as default } from './application';
|
||||||
|
@ -1,47 +1,37 @@
|
|||||||
import { uid } from '@nocobase/database';
|
import { uid } from '@nocobase/database';
|
||||||
import { Application } from './application';
|
import { Application } from './application';
|
||||||
|
import _ from 'lodash';
|
||||||
export interface PluginOptions {
|
|
||||||
app?: Application;
|
|
||||||
name?: string;
|
|
||||||
activate?: boolean;
|
|
||||||
displayName?: string;
|
|
||||||
description?: string;
|
|
||||||
version?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPlugin {
|
export interface IPlugin {
|
||||||
install?: (this: Plugin) => void;
|
install?: (this: Plugin) => void;
|
||||||
load?: (this: Plugin) => void;
|
load?: (this: Plugin) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PluginOptions {
|
||||||
|
name?: string;
|
||||||
|
activate?: boolean;
|
||||||
|
displayName?: string;
|
||||||
|
description?: string;
|
||||||
|
version?: string;
|
||||||
|
install?: (this: Plugin) => void;
|
||||||
|
load?: (this: Plugin) => void;
|
||||||
|
plugin?: typeof Plugin;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export type PluginFn = (this: Plugin) => void;
|
export type PluginFn = (this: Plugin) => void;
|
||||||
|
|
||||||
export type PluginType = string | PluginFn | typeof Plugin | IPlugin;
|
export type PluginType = string | PluginFn | typeof Plugin;
|
||||||
|
|
||||||
export class Plugin implements IPlugin {
|
export class Plugin implements IPlugin {
|
||||||
options: PluginOptions = {};
|
options: PluginOptions = {};
|
||||||
app: Application;
|
app: Application;
|
||||||
callbacks: IPlugin = {};
|
callbacks: IPlugin = {};
|
||||||
|
|
||||||
constructor(plugin?: PluginType, options?: PluginOptions) {
|
constructor(options?: PluginOptions & { app?: Application }) {
|
||||||
this.app = options?.app;
|
this.app = options?.app;
|
||||||
this.options = options || {};
|
this.options = options;
|
||||||
if (typeof plugin === 'function') {
|
this.callbacks = _.pick(options, ['load', 'activate']);
|
||||||
if (!this.options?.name && plugin.name) {
|
|
||||||
this.options.name = plugin.name;
|
|
||||||
}
|
|
||||||
this.callbacks.load = plugin as any;
|
|
||||||
} else if (
|
|
||||||
typeof plugin === 'object' &&
|
|
||||||
plugin.constructor === {}.constructor
|
|
||||||
) {
|
|
||||||
this.callbacks = plugin;
|
|
||||||
}
|
|
||||||
const cName = this.constructor.name;
|
|
||||||
if (this.options && !this.options?.name && cName && cName !== 'Plugin') {
|
|
||||||
this.options.name = cName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
66
yarn.lock
66
yarn.lock
@ -2381,6 +2381,17 @@
|
|||||||
"@types/yargs" "^15.0.0"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
|
|
||||||
|
"@jest/types@^27.1.1":
|
||||||
|
version "27.1.1"
|
||||||
|
resolved "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad"
|
||||||
|
integrity sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==
|
||||||
|
dependencies:
|
||||||
|
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||||
|
"@types/istanbul-reports" "^3.0.0"
|
||||||
|
"@types/node" "*"
|
||||||
|
"@types/yargs" "^16.0.0"
|
||||||
|
chalk "^4.0.0"
|
||||||
|
|
||||||
"@juggle/resize-observer@^3.3.1":
|
"@juggle/resize-observer@^3.3.1":
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
|
resolved "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
|
||||||
@ -3758,12 +3769,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/istanbul-lib-report" "*"
|
"@types/istanbul-lib-report" "*"
|
||||||
|
|
||||||
"@types/jest@^24.0.18":
|
"@types/jest@^27.0.1":
|
||||||
version "24.9.1"
|
version "27.0.1"
|
||||||
resolved "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534"
|
resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz#fafcc997da0135865311bb1215ba16dba6bdf4ca"
|
||||||
integrity sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q==
|
integrity sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==
|
||||||
dependencies:
|
dependencies:
|
||||||
jest-diff "^24.3.0"
|
jest-diff "^27.0.0"
|
||||||
|
pretty-format "^27.0.0"
|
||||||
|
|
||||||
"@types/js-cookie@^2.2.6":
|
"@types/js-cookie@^2.2.6":
|
||||||
version "2.2.7"
|
version "2.2.7"
|
||||||
@ -4103,6 +4115,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@types/yargs-parser" "*"
|
||||||
|
|
||||||
|
"@types/yargs@^16.0.0":
|
||||||
|
version "16.0.4"
|
||||||
|
resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
|
||||||
|
integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==
|
||||||
|
dependencies:
|
||||||
|
"@types/yargs-parser" "*"
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^4.9.1":
|
"@typescript-eslint/eslint-plugin@^4.9.1":
|
||||||
version "4.29.1"
|
version "4.29.1"
|
||||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz#808d206e2278e809292b5de752a91105da85860b"
|
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz#808d206e2278e809292b5de752a91105da85860b"
|
||||||
@ -4932,6 +4951,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
ansi-styles@^5.0.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||||
|
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||||
|
|
||||||
ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
|
ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
|
resolved "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
|
||||||
@ -7666,6 +7690,11 @@ diff-sequences@^26.6.2:
|
|||||||
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||||
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
|
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
|
||||||
|
|
||||||
|
diff-sequences@^27.0.6:
|
||||||
|
version "27.0.6"
|
||||||
|
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723"
|
||||||
|
integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==
|
||||||
|
|
||||||
diff@^4.0.1:
|
diff@^4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||||
@ -11153,7 +11182,7 @@ jest-config@^26.6.3:
|
|||||||
micromatch "^4.0.2"
|
micromatch "^4.0.2"
|
||||||
pretty-format "^26.6.2"
|
pretty-format "^26.6.2"
|
||||||
|
|
||||||
jest-diff@^24.3.0, jest-diff@^24.9.0:
|
jest-diff@^24.9.0:
|
||||||
version "24.9.0"
|
version "24.9.0"
|
||||||
resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
|
resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
|
||||||
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
|
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
|
||||||
@ -11173,6 +11202,16 @@ jest-diff@^26.6.2:
|
|||||||
jest-get-type "^26.3.0"
|
jest-get-type "^26.3.0"
|
||||||
pretty-format "^26.6.2"
|
pretty-format "^26.6.2"
|
||||||
|
|
||||||
|
jest-diff@^27.0.0:
|
||||||
|
version "27.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz#bda761c360f751bab1e7a2fe2fc2b0a35ce8518c"
|
||||||
|
integrity sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.0.0"
|
||||||
|
diff-sequences "^27.0.6"
|
||||||
|
jest-get-type "^27.0.6"
|
||||||
|
pretty-format "^27.2.0"
|
||||||
|
|
||||||
jest-docblock@^24.3.0:
|
jest-docblock@^24.3.0:
|
||||||
version "24.9.0"
|
version "24.9.0"
|
||||||
resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
|
resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
|
||||||
@ -11279,6 +11318,11 @@ jest-get-type@^26.3.0:
|
|||||||
resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
|
resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
|
||||||
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
|
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
|
||||||
|
|
||||||
|
jest-get-type@^27.0.6:
|
||||||
|
version "27.0.6"
|
||||||
|
resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz#0eb5c7f755854279ce9b68a9f1a4122f69047cfe"
|
||||||
|
integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==
|
||||||
|
|
||||||
jest-haste-map@^24.9.0:
|
jest-haste-map@^24.9.0:
|
||||||
version "24.9.0"
|
version "24.9.0"
|
||||||
resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
|
resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
|
||||||
@ -15401,6 +15445,16 @@ pretty-format@^26.6.2:
|
|||||||
ansi-styles "^4.0.0"
|
ansi-styles "^4.0.0"
|
||||||
react-is "^17.0.1"
|
react-is "^17.0.1"
|
||||||
|
|
||||||
|
pretty-format@^27.0.0, pretty-format@^27.2.0:
|
||||||
|
version "27.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz#ee37a94ce2a79765791a8649ae374d468c18ef19"
|
||||||
|
integrity sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==
|
||||||
|
dependencies:
|
||||||
|
"@jest/types" "^27.1.1"
|
||||||
|
ansi-regex "^5.0.0"
|
||||||
|
ansi-styles "^5.0.0"
|
||||||
|
react-is "^17.0.1"
|
||||||
|
|
||||||
printj@~1.1.0, printj@~1.1.2:
|
printj@~1.1.0, printj@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
||||||
|
Loading…
Reference in New Issue
Block a user