mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Merge pull request #9430 from strapi/i18n/lifecycles-rework
Add Lifecycles for i18n
This commit is contained in:
commit
9160c46a80
@ -398,7 +398,7 @@ const createOrUpdateTable = async ({ table, attributes, definition, ORM, model }
|
||||
|
||||
module.exports = async ({ ORM, loadedModel, definition, connection, model }) => {
|
||||
// run migrations
|
||||
await strapi.db.migrations.runMigration(migrateSchemas, {
|
||||
await strapi.db.migrations.run(migrateSchemas, {
|
||||
ORM,
|
||||
loadedModel,
|
||||
definition,
|
||||
|
||||
@ -301,7 +301,7 @@ module.exports = async ({ models, target }, ctx) => {
|
||||
const definitionDidChange = await didDefinitionChange(definition, instance);
|
||||
|
||||
// run migrations
|
||||
await strapi.db.migrations.runMigration(migrateSchema, {
|
||||
await strapi.db.migrations.run(migrateSchema, {
|
||||
definition,
|
||||
model: modelInstance,
|
||||
ORM: instance,
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const createLifecycleManager = require('../lifecycle-manager');
|
||||
|
||||
describe('Lifecycle Manager', () => {
|
||||
test('Allows registering lifecycles', () => {
|
||||
const manager = createLifecycleManager();
|
||||
|
||||
const lifecycle = {};
|
||||
manager.register(lifecycle);
|
||||
|
||||
expect(manager.lifecycles).toEqual([lifecycle]);
|
||||
});
|
||||
|
||||
test('Will run all the lifecycles if no model specified', async () => {
|
||||
const lifecycleA = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const lifecycleB = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const manager = createLifecycleManager();
|
||||
|
||||
manager.register(lifecycleA).register(lifecycleB);
|
||||
|
||||
await manager.run('find', { uid: 'test-uid' });
|
||||
|
||||
expect(lifecycleA.find).toHaveBeenCalled();
|
||||
expect(lifecycleB.find).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Will match on model if specified', async () => {
|
||||
const lifecycleA = {
|
||||
model: 'test-uid',
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const lifecycleB = {
|
||||
model: 'other-uid',
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const manager = createLifecycleManager();
|
||||
|
||||
manager.register(lifecycleA).register(lifecycleB);
|
||||
|
||||
await manager.run('find', { uid: 'test-uid' });
|
||||
|
||||
expect(lifecycleA.find).toHaveBeenCalled();
|
||||
expect(lifecycleB.find).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -7,6 +7,7 @@ const createConnectorRegistry = require('./connector-registry');
|
||||
const constants = require('./constants');
|
||||
const { validateModelSchemas } = require('./validation');
|
||||
const createMigrationManager = require('./migration-manager');
|
||||
const createLifecycleManager = require('./lifecycle-manager');
|
||||
|
||||
class DatabaseManager {
|
||||
constructor(strapi) {
|
||||
@ -23,6 +24,7 @@ class DatabaseManager {
|
||||
this.models = new Map();
|
||||
|
||||
this.migrations = createMigrationManager(this);
|
||||
this.lifecycles = createLifecycleManager();
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
|
||||
33
packages/strapi-database/lib/lifecycle-manager.js
Normal file
33
packages/strapi-database/lib/lifecycle-manager.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
const debug = require('debug')('strapi-database:lifecycle');
|
||||
const { isFunction, isNil } = require('lodash/fp');
|
||||
|
||||
class LifecycleManager {
|
||||
constructor() {
|
||||
debug('Initialize lifecycle manager');
|
||||
this.lifecycles = [];
|
||||
}
|
||||
|
||||
register(lifecycle) {
|
||||
debug('Register lifecycle');
|
||||
|
||||
this.lifecycles.push(lifecycle);
|
||||
return this;
|
||||
}
|
||||
|
||||
async run(action, model, ...args) {
|
||||
for (const lifecycle of this.lifecycles) {
|
||||
if (!isNil(lifecycle.model) && lifecycle.model !== model.uid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFunction(lifecycle[action])) {
|
||||
debug(`Run lifecycle ${action} for model ${model.uid}`);
|
||||
|
||||
await lifecycle[action](...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = () => new LifecycleManager();
|
||||
@ -14,7 +14,7 @@ class MigrationManager {
|
||||
this.migrations.push(migration);
|
||||
}
|
||||
|
||||
async runMigration(fn, options, context = {}) {
|
||||
async run(fn, options, context = {}) {
|
||||
debug('Run migration');
|
||||
await this.runBefore(options, context);
|
||||
await fn(options, context);
|
||||
|
||||
@ -4,6 +4,14 @@ const _ = require('lodash');
|
||||
const createQuery = require('../create-query');
|
||||
|
||||
describe('Database queries', () => {
|
||||
global.strapi = {
|
||||
db: {
|
||||
lifecycles: {
|
||||
run() {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Substitute id with primaryKey in parameters', () => {
|
||||
test.each(['create', 'update', 'delete', 'find', 'findOne', 'search', 'count', 'countSearch'])(
|
||||
'Calling "%s" replaces id by the primaryKey in the params of the model before calling the underlying connector',
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const executeLifecycle = async (lifecycle, model, ...args) => {
|
||||
// Run registered lifecycles
|
||||
await strapi.db.lifecycles.run(lifecycle, model, ...args);
|
||||
|
||||
// Run user lifecycles
|
||||
if (_.has(model, `lifecycles.${lifecycle}`)) {
|
||||
await model.lifecycles[lifecycle](...args);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { capitalize } = require('lodash/fp');
|
||||
const { getService } = require('../../utils');
|
||||
|
||||
@ -26,23 +25,23 @@ module.exports = async () => {
|
||||
await getService('locales').setDefaultLocale(DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
Object.values(strapi.models).forEach(model => {
|
||||
if (getService('content-types').isLocalized(model)) {
|
||||
// TODO: support adding lifecycles programmatically or connecting to a database event handler to avoid conflicts with existing lifecycles fonctions
|
||||
|
||||
_.set(model, 'lifecycles.beforeCreate', async data => {
|
||||
if (!data.locale) {
|
||||
data.locale = await getService('locales').getDefaultLocale();
|
||||
}
|
||||
Object.values(strapi.models)
|
||||
.filter(model => getService('content-types').isLocalized(model))
|
||||
.forEach(model => {
|
||||
strapi.db.lifecycles.register({
|
||||
model: model.uid,
|
||||
async beforeCreate(data) {
|
||||
await getService('localizations').assignDefaultLocale(data);
|
||||
},
|
||||
async afterCreate(entry) {
|
||||
await getService('localizations').addLocalizations(entry, { model });
|
||||
},
|
||||
async afterUpdate(entry) {
|
||||
await getService('localizations').updateNonLocalizedFields(entry, { model });
|
||||
},
|
||||
async afterDelete(entry) {
|
||||
await getService('localizations').removeEntryFromRelatedLocalizations(entry, { model });
|
||||
},
|
||||
});
|
||||
|
||||
_.set(model, 'lifecycles.afterCreate', async entry => {
|
||||
await getService('localizations').addLocalizations(entry, { model });
|
||||
});
|
||||
|
||||
_.set(model, 'lifecycles.afterUpdate', async entry => {
|
||||
await getService('localizations').updateNonLocalizedFields(entry, { model });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const { addLocalizations, updateNonLocalizedFields } = require('../localizations');
|
||||
const {
|
||||
assignDefaultLocale,
|
||||
addLocalizations,
|
||||
updateNonLocalizedFields,
|
||||
removeEntryFromRelatedLocalizations,
|
||||
} = require('../localizations');
|
||||
|
||||
const model = {
|
||||
uid: 'test-model',
|
||||
@ -20,12 +25,43 @@ const model = {
|
||||
};
|
||||
|
||||
describe('localizations service', () => {
|
||||
describe('assignDefaultLocale', () => {
|
||||
test('Does not change the input if locale is already defined', async () => {
|
||||
const input = { locale: 'myLocale' };
|
||||
await assignDefaultLocale(input);
|
||||
|
||||
expect(input).toStrictEqual({ locale: 'myLocale' });
|
||||
});
|
||||
|
||||
test('Use default locale to set the locale on the input data', async () => {
|
||||
const getDefaultLocaleMock = jest.fn(() => 'defaultLocale');
|
||||
|
||||
global.strapi = {
|
||||
plugins: {
|
||||
i18n: {
|
||||
services: {
|
||||
locales: {
|
||||
getDefaultLocale: getDefaultLocaleMock,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const input = {};
|
||||
await assignDefaultLocale(input);
|
||||
|
||||
expect(input).toStrictEqual({ locale: 'defaultLocale' });
|
||||
expect(getDefaultLocaleMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addLocalizations', () => {
|
||||
test('Does nothing if entry already as a localizations array', async () => {
|
||||
const entry = { localizations: [] };
|
||||
await addLocalizations(entry, { model });
|
||||
|
||||
expect(entry).toEqual({ localizations: [] });
|
||||
expect(entry).toStrictEqual({ localizations: [] });
|
||||
});
|
||||
|
||||
test('Updates entry in db', async () => {
|
||||
@ -58,7 +94,7 @@ describe('localizations service', () => {
|
||||
|
||||
await addLocalizations(entry, { model });
|
||||
|
||||
expect(entry).toEqual({
|
||||
expect(entry).toStrictEqual({
|
||||
id: entry.id,
|
||||
locale: entry.locale,
|
||||
localizations: [
|
||||
@ -127,4 +163,47 @@ describe('localizations service', () => {
|
||||
expect(update).toHaveBeenCalledWith({ id: 2 }, { stars: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeEntryFromRelatedLocalizations', () => {
|
||||
test('Does nothing if no localizations set', async () => {
|
||||
const update = jest.fn();
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { update };
|
||||
},
|
||||
};
|
||||
|
||||
const entry = { id: 1, locale: 'test' };
|
||||
|
||||
await removeEntryFromRelatedLocalizations(entry, { model });
|
||||
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Removes entry from localizations', async () => {
|
||||
const update = jest.fn();
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { update };
|
||||
},
|
||||
};
|
||||
|
||||
const entry = {
|
||||
id: 1,
|
||||
locale: 'mainLocale',
|
||||
localizations: [
|
||||
{ id: 1, locale: 'mainLocale' },
|
||||
{ id: 2, locale: 'otherLocale' },
|
||||
],
|
||||
};
|
||||
|
||||
await removeEntryFromRelatedLocalizations(entry, { model });
|
||||
|
||||
expect(update).toHaveBeenCalledTimes(1);
|
||||
expect(update).toHaveBeenCalledWith(
|
||||
{ id: 2 },
|
||||
{ localizations: [{ id: 2, locale: 'otherLocale' }] }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,8 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const { pick, isNil } = require('lodash/fp');
|
||||
|
||||
const { getService } = require('../utils');
|
||||
const { getNonLocalizedFields } = require('./content-types');
|
||||
|
||||
/**
|
||||
* Adds the default locale to an object if it isn't defined yet
|
||||
* @param {Object} data a data object before being persisted into db
|
||||
*/
|
||||
const assignDefaultLocale = async data => {
|
||||
if (isNil(data.locale)) {
|
||||
data.locale = await getService('locales').getDefaultLocale();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create default localizations for an entry if it isn't defined yet
|
||||
* @param {Object} entry entry to update
|
||||
* @param {Object} options
|
||||
* @param {Object} options.model corresponding model
|
||||
*/
|
||||
const addLocalizations = async (entry, { model }) => {
|
||||
if (isNil(entry.localizations)) {
|
||||
const localizations = [{ locale: entry.locale, id: entry.id }];
|
||||
@ -12,10 +30,16 @@ const addLocalizations = async (entry, { model }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update non localized fields of all the related localizations of an entry with the entry values
|
||||
* @param {Object} entry entry to update
|
||||
* @param {Object} options
|
||||
* @param {Object} options.model corresponding model
|
||||
*/
|
||||
const updateNonLocalizedFields = async (entry, { model }) => {
|
||||
const fieldsToUpdate = pick(getNonLocalizedFields(model), entry);
|
||||
|
||||
if (Array.isArray(entry.localizations)) {
|
||||
const fieldsToUpdate = pick(getNonLocalizedFields(model), entry);
|
||||
|
||||
const updateQueries = entry.localizations
|
||||
.filter(({ id }) => id != entry.id)
|
||||
.map(({ id }) => strapi.query(model.uid).update({ id }, fieldsToUpdate));
|
||||
@ -24,8 +48,28 @@ const updateNonLocalizedFields = async (entry, { model }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove entry from localizations & udpate related localizations
|
||||
* This method should be used only after an entry is deleted
|
||||
* @param {Object} entry entry to remove from localizations
|
||||
* @param {Object} options
|
||||
* @param {Object} options.model corresponding model
|
||||
*/
|
||||
const removeEntryFromRelatedLocalizations = async (entry, { model }) => {
|
||||
if (Array.isArray(entry.localizations)) {
|
||||
const newLocalizations = entry.localizations.filter(({ id }) => id != entry.id);
|
||||
|
||||
const updateQueries = newLocalizations.map(({ id }) => {
|
||||
return strapi.query(model.uid).update({ id }, { localizations: newLocalizations });
|
||||
});
|
||||
|
||||
await Promise.all(updateQueries);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
assignDefaultLocale,
|
||||
addLocalizations,
|
||||
updateNonLocalizedFields,
|
||||
getNonLocalizedFields,
|
||||
removeEntryFromRelatedLocalizations,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user