mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +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 { handleDatabaseError } = require('./utils/errors');
|
||||
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
||||
const pickCountFilters = omit(['sort', 'limit', 'start']);
|
||||
|
||||
@ -173,7 +175,10 @@ module.exports = function createQueryBuilder({ model, strapi }) {
|
||||
return wrapTransaction(runDelete, { transacting });
|
||||
}
|
||||
|
||||
async function deleteMany(params, { transacting } = {}) {
|
||||
async function deleteMany(
|
||||
params,
|
||||
{ transacting, returning = true, batchSize = BATCH_SIZE } = {}
|
||||
) {
|
||||
if (params[model.primaryKey]) {
|
||||
const entries = await find({ ...params, _limit: 1 }, null, { transacting });
|
||||
if (entries.length > 0) {
|
||||
@ -182,12 +187,30 @@ module.exports = function createQueryBuilder({ model, strapi }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const paramsWithDefaults = _.defaults(params, { _limit: -1 });
|
||||
const entries = await find(paramsWithDefaults, null, { transacting });
|
||||
return pmap(entries, entry => deleteOne(entry.id, { transacting }), {
|
||||
concurrency: 100,
|
||||
stopOnError: true,
|
||||
});
|
||||
if (returning) {
|
||||
const paramsWithDefaults = _.defaults(params, { _limit: -1 });
|
||||
const entries = await find(paramsWithDefaults, null, { transacting });
|
||||
return pmap(entries, entry => deleteOne(entry.id, { transacting }), {
|
||||
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) {
|
||||
|
||||
@ -5,12 +5,15 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
const { prop, omit } = require('lodash/fp');
|
||||
const pmap = require('p-map');
|
||||
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
|
||||
const { contentTypes: contentTypesUtils } = require('strapi-utils');
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const populateQueries = require('./utils/populate-queries');
|
||||
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
const { PUBLISHED_AT_ATTRIBUTE, DP_PUB_STATES } = contentTypesUtils.constants;
|
||||
const { findComponentByGlobalId } = require('./utils/helpers');
|
||||
const { handleDatabaseError } = require('./utils/errors');
|
||||
@ -505,7 +508,10 @@ module.exports = ({ model, strapi }) => {
|
||||
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]) {
|
||||
const entries = await find({ ...params, _limit: 1 }, null, { session });
|
||||
if (entries.length > 0) {
|
||||
@ -514,8 +520,28 @@ module.exports = ({ model, strapi }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries = await find(params, null, { session });
|
||||
return Promise.all(entries.map(entry => deleteOne(entry[model.primaryKey], { session })));
|
||||
if (returning) {
|
||||
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 } = {}) {
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"mongoose": "5.10.8",
|
||||
"mongoose-float": "^1.0.4",
|
||||
"mongoose-long": "^0.3.2",
|
||||
"p-map": "4.0.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"semver": "^7.3.4",
|
||||
"strapi-utils": "3.5.4"
|
||||
|
||||
@ -122,14 +122,35 @@ describe('Locales', () => {
|
||||
test('delete', async () => {
|
||||
const locale = { name: 'French', code: 'fr' };
|
||||
const deleteFn = jest.fn(() => locale);
|
||||
const query = jest.fn(() => ({ delete: deleteFn }));
|
||||
global.strapi = { query };
|
||||
const findOne = jest.fn(() => locale);
|
||||
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 });
|
||||
expect(query).toHaveBeenCalledWith('locale', 'i18n');
|
||||
expect(deleteFn).toHaveBeenCalledWith({ id: 1 });
|
||||
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', () => {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
const { isNil } = require('lodash/fp');
|
||||
const { DEFAULT_LOCALE } = require('../constants');
|
||||
const { getService } = 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 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 });
|
||||
|
||||
@ -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 = {
|
||||
find,
|
||||
findById,
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
const { omit } = require('lodash/fp');
|
||||
|
||||
const { createTestBuilder } = require('../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../test/helpers/request');
|
||||
|
||||
@ -13,11 +14,31 @@ const data = {
|
||||
const omitTimestamps = omit(['updatedAt', 'createdAt', 'updated_at', 'created_at']);
|
||||
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', () => {
|
||||
let rq;
|
||||
let strapi;
|
||||
const builder = createTestBuilder();
|
||||
|
||||
beforeAll(async () => {
|
||||
await builder.addContentType(productModel).build();
|
||||
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
});
|
||||
@ -167,7 +188,9 @@ describe('CRUD locales', () => {
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
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({
|
||||
url: `/i18n/locales/${data.locales[0].id}`,
|
||||
url: `/i18n/locales/${data.locales[1].id}`,
|
||||
method: 'PUT',
|
||||
body: localeUpdate,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
...omitTimestamps(data.locales[0]),
|
||||
...omitTimestamps(data.locales[1]),
|
||||
...localeUpdate,
|
||||
});
|
||||
data.locales[0] = res.body;
|
||||
data.locales[1] = res.body;
|
||||
});
|
||||
|
||||
test('Cannot update the code of a locale (without name)', async () => {
|
||||
@ -306,11 +329,54 @@ describe('CRUD locales', () => {
|
||||
});
|
||||
|
||||
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({
|
||||
url: `/i18n/locales/${data.locales[1].id}`,
|
||||
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.body).toMatchObject(omitTimestamps(data.locales[1]));
|
||||
data.deletedLocales.push(res.body);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user