strapi-plugin-import-export-entries
Version:
Import/Export data from and to your database in just few clicks.
366 lines (365 loc) • 17.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const fromPairs_1 = __importDefault(require("lodash/fromPairs"));
const fp_1 = require("lodash/fp");
const qs_1 = __importDefault(require("qs"));
const arrays_1 = require("../../../libs/arrays");
const constants_1 = require("../../config/constants");
const objects_1 = require("../../../libs/objects");
const models_1 = require("../../utils/models");
const converters_v2_1 = __importDefault(require("./converters-v2"));
const dataFormats = {
JSON: 'json',
};
const dataConverterConfigs = {
[dataFormats.JSON]: {
convertEntries: converters_v2_1.default.convertToJson,
},
};
module.exports = {
exportDataV2,
};
/**
* Export data.
*/
function exportDataV2({ slug, search, applySearch, deepness = 5, exportPluginsContentTypes, }) {
return __awaiter(this, void 0, void 0, function* () {
const slugsToExport = slug === constants_1.CustomSlugs.WHOLE_DB ? (0, models_1.getAllSlugs)({ includePluginsContentTypes: exportPluginsContentTypes }) : (0, arrays_1.toArray)(constants_1.CustomSlugToSlug[slug] || slug);
let store = {};
for (const slug of slugsToExport) {
const hierarchy = buildSlugHierarchy(slug, deepness);
store = yield findEntriesForHierarchy(store, slug, hierarchy, deepness, Object.assign({}, (applySearch ? { search } : {})));
}
const jsoContent = {
version: 2,
data: store,
};
const fileContent = convertData(jsoContent, {
dataFormat: 'json',
});
return fileContent;
});
}
function findEntriesForHierarchy(store, slug, hierarchy, deepness, { search, ids }) {
return __awaiter(this, void 0, void 0, function* () {
const schema = (0, models_1.getModel)(slug);
if (schema.uid === 'admin::user') {
return {};
}
let entries = yield findEntries(slug, deepness, { search, ids })
.then((entries) => {
var _a, _b;
entries = (0, arrays_1.toArray)(entries).filter(Boolean);
// Export locales
if ((_b = (_a = schema.pluginOptions) === null || _a === void 0 ? void 0 : _a.i18n) === null || _b === void 0 ? void 0 : _b.localized) {
const allEntries = [...entries];
const entryIdsToExported = (0, fromPairs_1.default)(allEntries.map((entry) => [entry.id, true]));
for (const entry of entries) {
(entry.localizations || []).forEach((localization) => {
if (localization.id && !entryIdsToExported[localization.id]) {
allEntries.push(localization);
entryIdsToExported[localization.id] = true;
}
});
}
return allEntries;
}
return entries;
})
.then((entries) => (0, arrays_1.toArray)(entries));
// Transform relations as ids.
let entriesFlatten = (0, cloneDeep_1.default)(entries);
(() => {
const flattenEntryCommon = (entry) => {
if (entry == null) {
return null;
}
else if ((0, arrays_1.isArraySafe)(entry)) {
return entry.map((rel) => {
if ((0, objects_1.isObjectSafe)(rel)) {
return rel.id;
}
return rel;
});
}
else if ((0, objects_1.isObjectSafe)(entry)) {
return entry.id;
}
return entry;
};
const flattenProperty = (propAttribute, propEntries) => {
if (propEntries == null) {
return null;
}
else if ((0, models_1.isComponentAttribute)(propAttribute)) {
return flattenEntryCommon(propEntries);
}
else if ((0, models_1.isDynamicZoneAttribute)(propAttribute)) {
return propEntries.map((entry) => ({
__component: entry.__component,
id: entry.id,
}));
}
else if ((0, models_1.isMediaAttribute)(propAttribute)) {
return flattenEntryCommon(propEntries);
}
else if ((0, models_1.isRelationAttribute)(propAttribute)) {
return flattenEntryCommon(propEntries);
}
return propEntries;
};
const flattenEntry = (entry, slug) => {
const attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['component', 'dynamiczone', 'media', 'relation'] });
for (const attribute of attributes) {
(0, models_1.setEntryProp)(entry, attribute.name, flattenProperty(attribute, (0, models_1.getEntryProp)(entry, attribute.name)));
}
return entry;
};
entriesFlatten = entriesFlatten.map((entry) => flattenEntry(entry, slug));
})();
store = (0, objects_1.mergeObjects)({ [slug]: Object.fromEntries(entriesFlatten.map((entry) => [entry.id, entry])) }, store);
// Skip admin::user slug.
const filterOutUnwantedRelations = () => {
const UNWANTED_RELATIONS = ['admin::user'];
const attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['relation'] });
return entries.map((entry) => {
attributes.forEach((attribute) => {
if (UNWANTED_RELATIONS.includes(attribute.target)) {
(0, models_1.deleteEntryProp)(entry, attribute.name);
}
});
return entry;
});
};
filterOutUnwantedRelations();
const findAndFlattenComponentAttributes = () => __awaiter(this, void 0, void 0, function* () {
var _a;
let attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['component'] });
for (const attribute of attributes) {
const attributeSlug = (_a = hierarchy[attribute.name]) === null || _a === void 0 ? void 0 : _a.__slug;
if (!attributeSlug) {
continue;
}
const ids = entries
.filter((entry) => !!(0, models_1.getEntryProp)(entry, attribute.name))
.flatMap((entry) => (0, models_1.getEntryProp)(entry, attribute.name))
.filter((entry) => !!entry.id)
.map((entry) => entry.id)
.filter((id) => { var _a; return typeof ((_a = store === null || store === void 0 ? void 0 : store[attributeSlug]) === null || _a === void 0 ? void 0 : _a[`${id}`]) === 'undefined'; });
const dataToStore = yield findEntriesForHierarchy(store, attributeSlug, hierarchy[attribute.name], deepness - 1, { ids });
store = (0, objects_1.mergeObjects)(dataToStore, store);
}
});
yield findAndFlattenComponentAttributes();
const findAndFlattenDynamicZoneAttributes = () => __awaiter(this, void 0, void 0, function* () {
var _b;
let attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['dynamiczone'] });
for (const attribute of attributes) {
for (const slugFromAttribute of attribute.components) {
const componentHierarchy = (_b = hierarchy[attribute.name]) === null || _b === void 0 ? void 0 : _b[slugFromAttribute];
const componentSlug = componentHierarchy === null || componentHierarchy === void 0 ? void 0 : componentHierarchy.__slug;
if (!componentSlug) {
continue;
}
const ids = entries
.filter((entry) => !!(0, models_1.getEntryProp)(entry, attribute.name))
.flatMap((entry) => (0, models_1.getEntryProp)(entry, attribute.name))
.filter((entry) => (entry === null || entry === void 0 ? void 0 : entry.__component) === slugFromAttribute)
.map((entry) => entry.id)
.filter((id) => { var _a; return typeof ((_a = store === null || store === void 0 ? void 0 : store[componentSlug]) === null || _a === void 0 ? void 0 : _a[`${id}`]) === 'undefined'; });
const dataToStore = yield findEntriesForHierarchy(store, componentSlug, componentHierarchy, deepness - 1, { ids });
store = (0, objects_1.mergeObjects)(dataToStore, store);
}
}
});
yield findAndFlattenDynamicZoneAttributes();
const findAndFlattenMediaAttributes = () => __awaiter(this, void 0, void 0, function* () {
var _c;
let attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['media'] });
for (const attribute of attributes) {
const attributeSlug = (_c = hierarchy[attribute.name]) === null || _c === void 0 ? void 0 : _c.__slug;
if (!attributeSlug) {
continue;
}
const ids = entries
.filter((entry) => !!(0, models_1.getEntryProp)(entry, attribute.name))
.flatMap((entry) => (0, models_1.getEntryProp)(entry, attribute.name))
.filter((entry) => !!entry.id)
.map((entry) => entry.id)
.filter((id) => { var _a; return typeof ((_a = store === null || store === void 0 ? void 0 : store[attributeSlug]) === null || _a === void 0 ? void 0 : _a[`${id}`]) === 'undefined'; });
const dataToStore = yield findEntriesForHierarchy(store, attributeSlug, hierarchy[attribute.name], deepness - 1, { ids });
store = (0, objects_1.mergeObjects)(dataToStore, store);
}
});
yield findAndFlattenMediaAttributes();
const findAndFlattenRelationAttributes = () => __awaiter(this, void 0, void 0, function* () {
var _d;
let attributes = (0, models_1.getModelAttributes)(slug, { filterType: ['relation'] });
for (const attribute of attributes) {
const attributeSlug = (_d = hierarchy[attribute.name]) === null || _d === void 0 ? void 0 : _d.__slug;
if (!attributeSlug) {
continue;
}
const ids = entries
.filter((entry) => !!(0, models_1.getEntryProp)(entry, attribute.name))
.flatMap((entry) => (0, models_1.getEntryProp)(entry, attribute.name))
.filter((entry) => !!entry.id)
.map((entry) => entry.id)
.filter((id) => { var _a; return typeof ((_a = store === null || store === void 0 ? void 0 : store[attributeSlug]) === null || _a === void 0 ? void 0 : _a[`${id}`]) === 'undefined'; });
const dataToStore = yield findEntriesForHierarchy(store, attributeSlug, hierarchy[attribute.name], deepness - 1, { ids });
store = (0, objects_1.mergeObjects)(dataToStore, store);
}
});
yield findAndFlattenRelationAttributes();
return store;
});
}
function findEntries(slug, deepness, { search, ids }) {
return __awaiter(this, void 0, void 0, function* () {
try {
const queryBuilder = new objects_1.ObjectBuilder();
queryBuilder.extend(getPopulateFromSchema(slug, deepness));
if (search) {
queryBuilder.extend(buildFilterQuery(search));
}
else if (ids) {
queryBuilder.extend({
filters: {
id: { $in: ids },
},
});
}
const entries = yield strapi.entityService.findMany(slug, queryBuilder.get());
return entries;
}
catch (_) {
return [];
}
});
}
function buildFilterQuery(search = '') {
let { filters, sort: sortRaw } = qs_1.default.parse(search);
// TODO: improve query parsing
const [attr, value] = (sortRaw === null || sortRaw === void 0 ? void 0 : sortRaw.split(':')) || [];
const sort = {};
if (attr && value) {
sort[attr] = value.toLowerCase();
}
return {
filters,
sort,
};
}
function convertData(exportContent, options) {
const converter = getConverter(options.dataFormat);
const convertedData = converter.convertEntries(exportContent, options);
return convertedData;
}
function getConverter(dataFormat) {
const converter = dataConverterConfigs[dataFormat];
if (!converter) {
throw new Error(`Data format ${dataFormat} is not supported.`);
}
return converter;
}
function getPopulateFromSchema(slug, deepness = 5) {
if (deepness <= 1) {
return true;
}
if (slug === 'admin::user') {
return undefined;
}
const populate = {};
const model = strapi.getModel(slug);
for (const [attributeName, attribute] of Object.entries(getModelPopulationAttributes(model))) {
if (!attribute) {
continue;
}
if ((0, models_1.isComponentAttribute)(attribute)) {
populate[attributeName] = getPopulateFromSchema(attribute.component, deepness - 1);
}
else if ((0, models_1.isDynamicZoneAttribute)(attribute)) {
const dynamicPopulate = attribute.components.reduce((zonePopulate, component) => {
const compPopulate = getPopulateFromSchema(component, deepness - 1);
return compPopulate === true ? zonePopulate : (0, fp_1.merge)(zonePopulate, compPopulate);
}, {});
populate[attributeName] = (0, fp_1.isEmpty)(dynamicPopulate) ? true : dynamicPopulate;
}
else if ((0, models_1.isRelationAttribute)(attribute)) {
const relationPopulate = getPopulateFromSchema(attribute.target, deepness - 1);
if (relationPopulate) {
populate[attributeName] = relationPopulate;
}
}
else if ((0, models_1.isMediaAttribute)(attribute)) {
populate[attributeName] = true;
}
}
return (0, fp_1.isEmpty)(populate) ? true : { populate };
}
// type Hierarchy = {
// [key: string]: Hierarchy | string;
// };
function buildSlugHierarchy(slug, deepness = 5) {
slug = constants_1.CustomSlugToSlug[slug] || slug;
if (deepness <= 1) {
return { __slug: slug };
}
const hierarchy = {
__slug: slug,
};
const model = (0, models_1.getModel)(slug);
for (const [attributeName, attribute] of Object.entries(getModelPopulationAttributes(model))) {
if (!attribute) {
continue;
}
if ((0, models_1.isComponentAttribute)(attribute)) {
hierarchy[attributeName] = buildSlugHierarchy(attribute.component, deepness - 1);
}
else if ((0, models_1.isDynamicZoneAttribute)(attribute)) {
hierarchy[attributeName] = Object.fromEntries(attribute.components.map((componentSlug) => [componentSlug, buildSlugHierarchy(componentSlug, deepness - 1)]));
}
else if ((0, models_1.isRelationAttribute)(attribute)) {
const relationHierarchy = buildSlugHierarchy(attribute.target, deepness - 1);
if (relationHierarchy) {
hierarchy[attributeName] = relationHierarchy;
}
}
else if ((0, models_1.isMediaAttribute)(attribute)) {
// TODO: remove casting
hierarchy[attributeName] = buildSlugHierarchy(constants_1.CustomSlugs.MEDIA, deepness - 1);
}
}
return hierarchy;
}
function getModelPopulationAttributes(model) {
if (model.uid === 'plugin::upload.file') {
const _a = model.attributes, { related } = _a, attributes = __rest(_a, ["related"]);
return attributes;
}
return model.attributes;
}