mirror of
https://github.com/strapi/strapi.git
synced 2025-11-11 15:49:50 +00:00
Delete all entries in a locale when locale is deleted
This commit is contained in:
parent
6b6e3eba55
commit
3c85f38fb4
@ -11,6 +11,8 @@ const { contentTypes: contentTypesUtils } = require('strapi-utils');
|
|||||||
const { singular } = require('pluralize');
|
const { singular } = require('pluralize');
|
||||||
const { handleDatabaseError } = require('./utils/errors');
|
const { handleDatabaseError } = require('./utils/errors');
|
||||||
|
|
||||||
|
const BATCH_SIZE = 1000;
|
||||||
|
|
||||||
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
||||||
const pickCountFilters = omit(['sort', 'limit', 'start']);
|
const pickCountFilters = omit(['sort', 'limit', 'start']);
|
||||||
|
|
||||||
@ -173,7 +175,10 @@ module.exports = function createQueryBuilder({ model, strapi }) {
|
|||||||
return wrapTransaction(runDelete, { transacting });
|
return wrapTransaction(runDelete, { transacting });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteMany(params, { transacting } = {}) {
|
async function deleteMany(
|
||||||
|
params,
|
||||||
|
{ transacting, returning = true, batchSize = BATCH_SIZE } = {}
|
||||||
|
) {
|
||||||
if (params[model.primaryKey]) {
|
if (params[model.primaryKey]) {
|
||||||
const entries = await find({ ...params, _limit: 1 }, null, { transacting });
|
const entries = await find({ ...params, _limit: 1 }, null, { transacting });
|
||||||
if (entries.length > 0) {
|
if (entries.length > 0) {
|
||||||
@ -182,12 +187,30 @@ module.exports = function createQueryBuilder({ model, strapi }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramsWithDefaults = _.defaults(params, { _limit: -1 });
|
if (returning) {
|
||||||
const entries = await find(paramsWithDefaults, null, { transacting });
|
const paramsWithDefaults = _.defaults(params, { _limit: -1 });
|
||||||
return pmap(entries, entry => deleteOne(entry.id, { transacting }), {
|
const entries = await find(paramsWithDefaults, null, { transacting });
|
||||||
concurrency: 100,
|
return pmap(entries, entry => deleteOne(entry.id, { transacting }), {
|
||||||
stopOnError: true,
|
concurrency: 100,
|
||||||
});
|
stopOnError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning false, we can optimize the function
|
||||||
|
const batchParams = _.assign({}, params, { _limit: batchSize, _sort: 'id:ASC' });
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
const batch = await find(batchParams, null, { transacting });
|
||||||
|
|
||||||
|
await pmap(batch, entry => deleteOne(entry.id, { transacting }), {
|
||||||
|
concurrency: 100,
|
||||||
|
stopOnError: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (batch.length < BATCH_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function search(params, populate) {
|
function search(params, populate) {
|
||||||
|
|||||||
@ -5,12 +5,15 @@
|
|||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { prop, omit } = require('lodash/fp');
|
const { prop, omit } = require('lodash/fp');
|
||||||
|
const pmap = require('p-map');
|
||||||
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
|
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
|
||||||
const { contentTypes: contentTypesUtils } = require('strapi-utils');
|
const { contentTypes: contentTypesUtils } = require('strapi-utils');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
const populateQueries = require('./utils/populate-queries');
|
const populateQueries = require('./utils/populate-queries');
|
||||||
|
|
||||||
|
const BATCH_SIZE = 1000;
|
||||||
|
|
||||||
const { PUBLISHED_AT_ATTRIBUTE, DP_PUB_STATES } = contentTypesUtils.constants;
|
const { PUBLISHED_AT_ATTRIBUTE, DP_PUB_STATES } = contentTypesUtils.constants;
|
||||||
const { findComponentByGlobalId } = require('./utils/helpers');
|
const { findComponentByGlobalId } = require('./utils/helpers');
|
||||||
const { handleDatabaseError } = require('./utils/errors');
|
const { handleDatabaseError } = require('./utils/errors');
|
||||||
@ -505,7 +508,10 @@ module.exports = ({ model, strapi }) => {
|
|||||||
return model.updateRelations(Object.assign(params, { values: relations }), { session });
|
return model.updateRelations(Object.assign(params, { values: relations }), { session });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteMany(params, { session = null } = {}) {
|
async function deleteMany(
|
||||||
|
params,
|
||||||
|
{ session = null, returning = true, batchSize = BATCH_SIZE } = {}
|
||||||
|
) {
|
||||||
if (params[model.primaryKey]) {
|
if (params[model.primaryKey]) {
|
||||||
const entries = await find({ ...params, _limit: 1 }, null, { session });
|
const entries = await find({ ...params, _limit: 1 }, null, { session });
|
||||||
if (entries.length > 0) {
|
if (entries.length > 0) {
|
||||||
@ -514,8 +520,28 @@ module.exports = ({ model, strapi }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = await find(params, null, { session });
|
if (returning) {
|
||||||
return Promise.all(entries.map(entry => deleteOne(entry[model.primaryKey], { session })));
|
const entries = await find(params, null, { session });
|
||||||
|
return pmap(entries, entry => deleteOne(entry[model.primaryKey], { session }), {
|
||||||
|
concurrency: 100,
|
||||||
|
stopOnError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning false, we can optimize the function
|
||||||
|
const batchParams = _.assign({}, params, { _limit: batchSize, _sort: 'id:ASC' });
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
const batch = await find(batchParams, null, { session });
|
||||||
|
await pmap(batch, entry => deleteOne(entry[model.primaryKey], { session }), {
|
||||||
|
concurrency: 100,
|
||||||
|
stopOnError: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (batch.length < BATCH_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteOne(id, { session = null } = {}) {
|
async function deleteOne(id, { session = null } = {}) {
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"mongoose": "5.10.8",
|
"mongoose": "5.10.8",
|
||||||
"mongoose-float": "^1.0.4",
|
"mongoose-float": "^1.0.4",
|
||||||
"mongoose-long": "^0.3.2",
|
"mongoose-long": "^0.3.2",
|
||||||
|
"p-map": "4.0.0",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"semver": "^7.3.4",
|
"semver": "^7.3.4",
|
||||||
"strapi-utils": "3.5.4"
|
"strapi-utils": "3.5.4"
|
||||||
|
|||||||
@ -122,14 +122,35 @@ describe('Locales', () => {
|
|||||||
test('delete', async () => {
|
test('delete', async () => {
|
||||||
const locale = { name: 'French', code: 'fr' };
|
const locale = { name: 'French', code: 'fr' };
|
||||||
const deleteFn = jest.fn(() => locale);
|
const deleteFn = jest.fn(() => locale);
|
||||||
const query = jest.fn(() => ({ delete: deleteFn }));
|
const findOne = jest.fn(() => locale);
|
||||||
global.strapi = { query };
|
const isLocalized = jest.fn(() => true);
|
||||||
|
const query = jest.fn(() => ({ delete: deleteFn, findOne }));
|
||||||
|
global.strapi = {
|
||||||
|
query,
|
||||||
|
plugins: { i18n: { services: { 'content-types': { isLocalized } } } },
|
||||||
|
contentTypes: { 'application::country.country': {} },
|
||||||
|
};
|
||||||
|
|
||||||
const deletedLocale = await localesService.delete({ id: 1 });
|
const deletedLocale = await localesService.delete({ id: 1 });
|
||||||
expect(query).toHaveBeenCalledWith('locale', 'i18n');
|
expect(query).toHaveBeenCalledWith('locale', 'i18n');
|
||||||
expect(deleteFn).toHaveBeenCalledWith({ id: 1 });
|
expect(deleteFn).toHaveBeenCalledWith({ id: 1 });
|
||||||
expect(deletedLocale).toMatchObject(locale);
|
expect(deletedLocale).toMatchObject(locale);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('delete - not found', async () => {
|
||||||
|
const locale = { name: 'French', code: 'fr' };
|
||||||
|
const deleteFn = jest.fn(() => locale);
|
||||||
|
const findOne = jest.fn(() => undefined);
|
||||||
|
const query = jest.fn(() => ({ delete: deleteFn, findOne }));
|
||||||
|
global.strapi = {
|
||||||
|
query,
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletedLocale = await localesService.delete({ id: 1 });
|
||||||
|
expect(query).toHaveBeenCalledWith('locale', 'i18n');
|
||||||
|
expect(deleteFn).not.toHaveBeenCalled();
|
||||||
|
expect(deletedLocale).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initDefaultLocale', () => {
|
describe('initDefaultLocale', () => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const { isNil } = require('lodash/fp');
|
const { isNil } = require('lodash/fp');
|
||||||
const { DEFAULT_LOCALE } = require('../constants');
|
const { DEFAULT_LOCALE } = require('../constants');
|
||||||
|
const { getService } = require('../utils');
|
||||||
|
|
||||||
const { getCoreStore } = require('../utils');
|
const { getCoreStore } = require('../utils');
|
||||||
|
|
||||||
@ -15,7 +16,15 @@ const create = locale => strapi.query('locale', 'i18n').create(locale);
|
|||||||
|
|
||||||
const update = (params, updates) => strapi.query('locale', 'i18n').update(params, updates);
|
const update = (params, updates) => strapi.query('locale', 'i18n').update(params, updates);
|
||||||
|
|
||||||
const deleteFn = ({ id }) => strapi.query('locale', 'i18n').delete({ id });
|
const deleteFn = async ({ id }) => {
|
||||||
|
let localeToDelete = await strapi.query('locale', 'i18n').findOne({ id });
|
||||||
|
|
||||||
|
if (localeToDelete) {
|
||||||
|
await deleteAllLocalizedEntriesFor({ locale: localeToDelete.code });
|
||||||
|
localeToDelete = await strapi.query('locale', 'i18n').delete({ id });
|
||||||
|
}
|
||||||
|
return localeToDelete;
|
||||||
|
};
|
||||||
|
|
||||||
const setDefaultLocale = ({ code }) => getCoreStore().set({ key: 'default_locale', value: code });
|
const setDefaultLocale = ({ code }) => getCoreStore().set({ key: 'default_locale', value: code });
|
||||||
|
|
||||||
@ -44,6 +53,16 @@ const initDefaultLocale = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteAllLocalizedEntriesFor = async ({ locale }) => {
|
||||||
|
const { isLocalized } = getService('content-types');
|
||||||
|
|
||||||
|
const localizedModels = Object.values(strapi.contentTypes).filter(isLocalized);
|
||||||
|
|
||||||
|
for (const model of localizedModels) {
|
||||||
|
await strapi.query(model.uid).delete({ locale }, { returning: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
find,
|
find,
|
||||||
findById,
|
findById,
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const { omit } = require('lodash/fp');
|
const { omit } = require('lodash/fp');
|
||||||
|
|
||||||
|
const { createTestBuilder } = require('../../../test/helpers/builder');
|
||||||
const { createStrapiInstance } = require('../../../test/helpers/strapi');
|
const { createStrapiInstance } = require('../../../test/helpers/strapi');
|
||||||
const { createAuthRequest } = require('../../../test/helpers/request');
|
const { createAuthRequest } = require('../../../test/helpers/request');
|
||||||
|
|
||||||
@ -13,11 +14,31 @@ const data = {
|
|||||||
const omitTimestamps = omit(['updatedAt', 'createdAt', 'updated_at', 'created_at']);
|
const omitTimestamps = omit(['updatedAt', 'createdAt', 'updated_at', 'created_at']);
|
||||||
const compareLocales = (a, b) => (a.code < b.code ? -1 : 1);
|
const compareLocales = (a, b) => (a.code < b.code ? -1 : 1);
|
||||||
|
|
||||||
|
const productModel = {
|
||||||
|
pluginOptions: {
|
||||||
|
i18n: {
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connection: 'default',
|
||||||
|
name: 'product',
|
||||||
|
description: '',
|
||||||
|
collectionName: '',
|
||||||
|
};
|
||||||
|
|
||||||
describe('CRUD locales', () => {
|
describe('CRUD locales', () => {
|
||||||
let rq;
|
let rq;
|
||||||
let strapi;
|
let strapi;
|
||||||
|
const builder = createTestBuilder();
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await builder.addContentType(productModel).build();
|
||||||
|
|
||||||
strapi = await createStrapiInstance();
|
strapi = await createStrapiInstance();
|
||||||
rq = await createAuthRequest({ strapi });
|
rq = await createAuthRequest({ strapi });
|
||||||
});
|
});
|
||||||
@ -167,7 +188,9 @@ describe('CRUD locales', () => {
|
|||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.body).toHaveLength(data.locales.length);
|
expect(res.body).toHaveLength(data.locales.length);
|
||||||
expect(res.body.sort(compareLocales)).toMatchObject(data.locales.sort(compareLocales));
|
expect(res.body.sort(compareLocales)).toMatchObject(
|
||||||
|
data.locales.slice().sort(compareLocales)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -179,17 +202,17 @@ describe('CRUD locales', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let res = await rq({
|
let res = await rq({
|
||||||
url: `/i18n/locales/${data.locales[0].id}`,
|
url: `/i18n/locales/${data.locales[1].id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: localeUpdate,
|
body: localeUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.body).toMatchObject({
|
expect(res.body).toMatchObject({
|
||||||
...omitTimestamps(data.locales[0]),
|
...omitTimestamps(data.locales[1]),
|
||||||
...localeUpdate,
|
...localeUpdate,
|
||||||
});
|
});
|
||||||
data.locales[0] = res.body;
|
data.locales[1] = res.body;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Cannot update the code of a locale (without name)', async () => {
|
test('Cannot update the code of a locale (without name)', async () => {
|
||||||
@ -306,11 +329,54 @@ describe('CRUD locales', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can delete a locale', async () => {
|
test('Can delete a locale', async () => {
|
||||||
|
const { body: frenchProduct } = await rq({
|
||||||
|
url: '/content-manager/collection-types/application::product.product',
|
||||||
|
method: 'POST',
|
||||||
|
qs: { plugins: { i18n: { locale: 'fr' } } },
|
||||||
|
body: { name: 'product name' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await rq({
|
||||||
|
url: '/content-manager/collection-types/application::product.product',
|
||||||
|
method: 'POST',
|
||||||
|
qs: { plugins: { i18n: { locale: 'en', relatedEntityId: frenchProduct.id } } },
|
||||||
|
body: { name: 'product name' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: createdProducts },
|
||||||
|
} = await rq({
|
||||||
|
url: '/content-manager/collection-types/application::product.product',
|
||||||
|
method: 'GET',
|
||||||
|
qs: { _locale: 'fr' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createdProducts).toHaveLength(1);
|
||||||
|
expect(createdProducts[0].localizations[0].locale).toBe('en');
|
||||||
|
|
||||||
const res = await rq({
|
const res = await rq({
|
||||||
url: `/i18n/locales/${data.locales[1].id}`,
|
url: `/i18n/locales/${data.locales[1].id}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: frenchProducts },
|
||||||
|
} = await rq({
|
||||||
|
url: '/content-manager/collection-types/application::product.product',
|
||||||
|
method: 'GET',
|
||||||
|
qs: { _locale: 'fr' },
|
||||||
|
});
|
||||||
|
expect(frenchProducts).toHaveLength(0);
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: englishProducts },
|
||||||
|
} = await rq({
|
||||||
|
url: '/content-manager/collection-types/application::product.product',
|
||||||
|
method: 'GET',
|
||||||
|
qs: { _locale: 'en' },
|
||||||
|
});
|
||||||
|
expect(englishProducts).toHaveLength(1);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.body).toMatchObject(omitTimestamps(data.locales[1]));
|
expect(res.body).toMatchObject(omitTimestamps(data.locales[1]));
|
||||||
data.deletedLocales.push(res.body);
|
data.deletedLocales.push(res.body);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user