feat: collection manager schema env (#1506)
* feat: database manager schema env support * feat: skip db2cm collection in custom schema * fix: test * test: through table custom schema * chore: fields hooks * chore: reload through collection on autoCreate * fix: test error * fix: skip test case * chore: children test --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
acc5656490
commit
9048b2a58b
@ -153,6 +153,15 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
||||
static async sync(options) {
|
||||
const model = this as any;
|
||||
|
||||
const _schema = model._schema;
|
||||
|
||||
if (_schema && _schema != 'public') {
|
||||
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${_schema}";`, {
|
||||
raw: true,
|
||||
transaction: options?.transaction,
|
||||
});
|
||||
}
|
||||
|
||||
// fix sequelize sync with model that not have any column
|
||||
if (Object.keys(model.tableAttributes).length === 0) {
|
||||
if (this.database.inDialect('sqlite', 'mysql')) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Database, { Collection as DBCollection } from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
import { createApp } from '.';
|
||||
import CollectionManagerPlugin from '@nocobase/plugin-collection-manager';
|
||||
|
||||
describe('collections repository', () => {
|
||||
let db: Database;
|
||||
@ -19,6 +20,49 @@ describe('collections repository', () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('should set collection schema from env', async () => {
|
||||
if (!db.inDialect('postgres')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugin = app.getPlugin<CollectionManagerPlugin>('collection-manager');
|
||||
plugin.schema = 'testSchema';
|
||||
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'posts',
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
const postsCollection = db.getCollection('posts');
|
||||
expect(postsCollection.options.schema).toEqual('testSchema');
|
||||
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'tags',
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
await Field.repository.create({
|
||||
values: {
|
||||
name: 'posts',
|
||||
type: 'belongsToMany',
|
||||
target: 'posts',
|
||||
through: 'posts_tags',
|
||||
foreignKey: 'tag_id',
|
||||
otherKey: 'post_id',
|
||||
interface: 'm2m',
|
||||
collectionName: 'tags',
|
||||
},
|
||||
context: {},
|
||||
});
|
||||
|
||||
const throughCollection = db.getCollection('posts_tags');
|
||||
expect(throughCollection.options.schema).toEqual('testSchema');
|
||||
});
|
||||
|
||||
test('create underscored field', async () => {
|
||||
if (process.env.DB_UNDERSCORED !== 'true') {
|
||||
return;
|
||||
|
@ -1,78 +0,0 @@
|
||||
import Database, { Collection as DBCollection } from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
import { createApp } from '..';
|
||||
|
||||
describe('children options', () => {
|
||||
let db: Database;
|
||||
let app: Application;
|
||||
let Collection: DBCollection;
|
||||
let Field: DBCollection;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createApp();
|
||||
db = app.db;
|
||||
Collection = db.getCollection('collections');
|
||||
Field = db.getCollection('fields');
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'tests',
|
||||
},
|
||||
});
|
||||
await Collection.repository.create({
|
||||
values: {
|
||||
name: 'foos',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('when there are no children, the target collection is not created', async () => {
|
||||
const field = await Field.repository.create({
|
||||
values: {
|
||||
type: 'hasMany',
|
||||
collectionName: 'tests',
|
||||
},
|
||||
});
|
||||
const json = field.toJSON();
|
||||
expect(json).toMatchObject({
|
||||
type: 'hasMany',
|
||||
collectionName: 'tests',
|
||||
sourceKey: 'id',
|
||||
targetKey: 'id',
|
||||
});
|
||||
expect(json.name).toBeDefined();
|
||||
expect(json.target).toBeDefined();
|
||||
expect(json.foreignKey).toBeDefined();
|
||||
// 无 children 时,target collection 不创建
|
||||
const target = await Collection.model.findOne({
|
||||
where: {
|
||||
name: json.target,
|
||||
},
|
||||
});
|
||||
expect(target).toBeNull();
|
||||
});
|
||||
|
||||
it('the collectionName of the child field is the target of the parent field', async () => {
|
||||
const field = await Field.repository.create({
|
||||
values: {
|
||||
type: 'hasMany',
|
||||
collectionName: 'tests',
|
||||
children: [{ type: 'string' }, { type: 'string' }],
|
||||
},
|
||||
});
|
||||
const json = field.toJSON();
|
||||
const target = await Collection.model.findOne({
|
||||
where: {
|
||||
name: json.target,
|
||||
},
|
||||
});
|
||||
expect(target).toBeDefined();
|
||||
// 子字段的 collectionName 是父字段的 target
|
||||
for (const child of json.children) {
|
||||
expect(child.collectionName).toBe(json.target);
|
||||
}
|
||||
});
|
||||
});
|
@ -152,6 +152,7 @@ export function afterCreateForForeignKeyField(db: Database) {
|
||||
isThrough: true,
|
||||
sortable: false,
|
||||
},
|
||||
context,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
@ -66,10 +66,12 @@ export class CollectionRepository extends Repository {
|
||||
...field.options,
|
||||
});
|
||||
}
|
||||
|
||||
await this.create({
|
||||
values: {
|
||||
...options,
|
||||
fields,
|
||||
from: 'db2cm',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import path from 'path';
|
||||
|
||||
import lodash from 'lodash';
|
||||
import { UniqueConstraintError } from 'sequelize';
|
||||
|
||||
import PluginErrorHandler from '@nocobase/plugin-error-handler';
|
||||
@ -11,7 +9,6 @@ import { CollectionRepository } from '.';
|
||||
import {
|
||||
afterCreateForForeignKeyField,
|
||||
afterCreateForReverseField,
|
||||
beforeCreateForChildrenCollection,
|
||||
beforeCreateForReverseField,
|
||||
beforeDestroyForeignKey,
|
||||
beforeInitOptions,
|
||||
@ -19,9 +16,17 @@ import {
|
||||
|
||||
import { InheritedCollection } from '@nocobase/database';
|
||||
import { CollectionModel, FieldModel } from './models';
|
||||
import * as process from 'process';
|
||||
import lodash from 'lodash';
|
||||
|
||||
export class CollectionManagerPlugin extends Plugin {
|
||||
public schema: string;
|
||||
|
||||
async beforeLoad() {
|
||||
if (process.env.COLLECTION_MANAGER_SCHEMA) {
|
||||
this.schema = process.env.COLLECTION_MANAGER_SCHEMA;
|
||||
}
|
||||
|
||||
this.app.db.registerModels({
|
||||
CollectionModel,
|
||||
FieldModel,
|
||||
@ -48,25 +53,29 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
],
|
||||
});
|
||||
|
||||
this.app.db.on('fields.beforeUpdate', async (model, options) => {
|
||||
const newValue = options.values;
|
||||
if (
|
||||
model.get('reverseKey') &&
|
||||
lodash.get(newValue, 'reverseField') &&
|
||||
!lodash.get(newValue, 'reverseField.key')
|
||||
) {
|
||||
const field = await this.app.db
|
||||
.getModel('fields')
|
||||
.findByPk(model.get('reverseKey'), { transaction: options.transaction });
|
||||
if (field) {
|
||||
throw new Error('cant update field without a reverseField key');
|
||||
}
|
||||
this.app.db.on('collections.beforeCreate', async (model) => {
|
||||
if (this.app.db.inDialect('postgres') && this.schema && model.get('from') != 'db2cm') {
|
||||
model.set('schema', this.schema);
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on(
|
||||
'collections.afterCreateWithAssociations',
|
||||
async (model: CollectionModel, { context, transaction }) => {
|
||||
if (context) {
|
||||
await model.migrate({
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.app.db.on('collections.beforeDestroy', async (model: CollectionModel, options) => {
|
||||
await model.remove(options);
|
||||
});
|
||||
|
||||
// 要在 beforeInitOptions 之前处理
|
||||
this.app.db.on('fields.beforeCreate', beforeCreateForReverseField(this.app.db));
|
||||
this.app.db.on('fields.beforeCreate', beforeCreateForChildrenCollection(this.app.db));
|
||||
|
||||
this.app.db.on('fields.beforeCreate', async (model, options) => {
|
||||
const collectionName = model.get('collectionName');
|
||||
@ -91,17 +100,6 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
|
||||
this.app.db.on('fields.afterCreate', afterCreateForReverseField(this.app.db));
|
||||
|
||||
this.app.db.on(
|
||||
'collections.afterCreateWithAssociations',
|
||||
async (model: CollectionModel, { context, transaction }) => {
|
||||
if (context) {
|
||||
await model.migrate({
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.app.db.on('fields.afterCreate', async (model: FieldModel, { context, transaction }) => {
|
||||
if (context) {
|
||||
await model.migrate({
|
||||
@ -114,6 +112,22 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
// after migrate
|
||||
this.app.db.on('fields.afterCreate', afterCreateForForeignKeyField(this.app.db));
|
||||
|
||||
this.app.db.on('fields.beforeUpdate', async (model, options) => {
|
||||
const newValue = options.values;
|
||||
if (
|
||||
model.get('reverseKey') &&
|
||||
lodash.get(newValue, 'reverseField') &&
|
||||
!lodash.get(newValue, 'reverseField.key')
|
||||
) {
|
||||
const field = await this.app.db
|
||||
.getModel('fields')
|
||||
.findByPk(model.get('reverseKey'), { transaction: options.transaction });
|
||||
if (field) {
|
||||
throw new Error('cant update field without a reverseField key');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterUpdate', async (model: FieldModel, { context, transaction }) => {
|
||||
const prevOptions = model.previous('options');
|
||||
const currentOptions = model.get('options');
|
||||
@ -158,10 +172,6 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
});
|
||||
});
|
||||
|
||||
this.app.db.on('collections.beforeDestroy', async (model: CollectionModel, options) => {
|
||||
await model.remove(options);
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterDestroy', async (model: FieldModel, options) => {
|
||||
const { transaction } = options;
|
||||
const collectionName = model.get('collectionName');
|
||||
|
Loading…
Reference in New Issue
Block a user