@strapi/data-transfer
Version:
Data transfer capabilities for Strapi
296 lines (293 loc) • 12.5 kB
JavaScript
import { isNil, clone } from 'lodash/fp';
// TODO: Remove any types when we'll have types for DB metadata
const createLinkQuery = (strapi, trx)=>{
const query = ()=>{
const { connection } = strapi.db;
// TODO: Export utils from database and use the addSchema that is already written
const addSchema = (tableName)=>{
const schemaName = connection.client.connectionSettings.schema;
return schemaName ? `${schemaName}.${tableName}` : tableName;
};
async function* generateAllForAttribute(uid, fieldName) {
const metadata = strapi.db.metadata.get(uid);
if (!metadata) {
throw new Error(`No metadata found for ${uid}`);
}
const attributes = filterValidRelationalAttributes(metadata.attributes);
if (!(fieldName in attributes)) {
throw new Error(`${fieldName} is not a valid relational attribute name`);
}
const attribute = attributes[fieldName];
const kind = getLinkKind(attribute, uid);
const { relation, target } = attribute;
// The relation is stored in the same table
// TODO: handle manyToOne joinColumn
if (attribute.joinColumn) {
const joinColumnName = attribute.joinColumn.name;
const qb = connection.queryBuilder().select('id', joinColumnName).from(addSchema(metadata.tableName));
if (trx) {
qb.transacting(trx);
}
// TODO: stream the query to improve performances
const entries = await qb;
for (const entry of entries){
const ref = entry[joinColumnName];
if (ref !== null) {
yield {
kind,
relation,
left: {
type: uid,
ref: entry.id,
field: fieldName
},
right: {
type: target,
ref
}
};
}
}
}
// The relation uses a join table
if (attribute.joinTable) {
const { name, joinColumn, inverseJoinColumn, orderColumnName, morphColumn, inverseOrderColumnName } = attribute.joinTable;
const qb = connection.queryBuilder().from(addSchema(name));
const columns = {
left: {
ref: null
},
right: {
ref: null
}
};
const left = {
type: uid,
field: fieldName
};
const right = {};
if (kind === 'relation.basic' || kind === 'relation.circular') {
right.type = attribute.target;
right.field = attribute.inversedBy;
columns.left.ref = joinColumn.name;
columns.right.ref = inverseJoinColumn.name;
if (orderColumnName) {
columns.left.order = orderColumnName;
}
if (inverseOrderColumnName) {
columns.right.order = inverseOrderColumnName;
}
}
if (kind === 'relation.morph') {
columns.left.ref = joinColumn.name;
columns.right.ref = morphColumn.idColumn.name;
columns.right.type = morphColumn.typeColumn.name;
columns.right.field = 'field';
columns.right.order = 'order';
}
const validColumns = [
// Left
columns.left.ref,
columns.left.order,
// Right
columns.right.ref,
columns.right.type,
columns.right.field,
columns.right.order
].filter((column)=>!isNil(column));
qb.select(validColumns);
if (trx) {
qb.transacting(trx);
}
// TODO: stream the query to improve performances
const entries = await qb;
for (const entry of entries){
if (columns.left.ref) {
left.ref = entry[columns.left.ref];
}
if (columns.right.ref) {
right.ref = entry[columns.right.ref];
}
if (columns.left.order) {
left.pos = entry[columns.left.order];
}
if (columns.right.order) {
right.pos = entry[columns.right.order];
}
if (columns.right.type) {
right.type = entry[columns.right.type];
}
if (columns.right.field) {
right.field = entry[columns.right.field];
}
const link = {
kind,
relation,
left: clone(left),
right: clone(right)
};
yield link;
}
}
if (attribute.morphColumn) {
const { typeColumn, idColumn } = attribute.morphColumn;
const qb = connection.queryBuilder().select('id', typeColumn.name, idColumn.name).from(addSchema(metadata.tableName)).whereNotNull(typeColumn.name).whereNotNull(idColumn.name);
if (trx) {
qb.transacting(trx);
}
const entries = await qb;
for (const entry of entries){
const ref = entry[idColumn.name];
yield {
kind,
relation,
left: {
type: uid,
ref: entry.id,
field: fieldName
},
right: {
type: entry[typeColumn.name],
ref
}
};
}
}
}
async function* generateAll(uid) {
const metadata = strapi.db.metadata.get(uid);
if (!metadata) {
throw new Error(`No metadata found for ${uid}`);
}
const attributes = filterValidRelationalAttributes(metadata.attributes);
for (const fieldName of Object.keys(attributes)){
for await (const link of generateAllForAttribute(uid, fieldName)){
yield link;
}
}
}
const insert = async (link)=>{
const { kind, left, right } = link;
const metadata = strapi.db.metadata.get(left.type);
const attribute = metadata.attributes[left.field];
const payload = {};
/**
* This _should_ only happen for attributes that are added dynamically e.g. review-workflow stages
* and a user is importing EE data into a CE project.
*/ if (!attribute) {
return;
}
if (attribute.type !== 'relation') {
throw new Error(`Attribute ${left.field} is not a relation`);
}
if ('joinColumn' in attribute && attribute.joinColumn) {
const joinColumnName = attribute.joinColumn.name;
// Note: this addSchema may not be necessary, but is added for safety
const qb = connection(addSchema(metadata.tableName)).where('id', left.ref).update({
[joinColumnName]: right.ref
});
if (trx) {
qb.transacting(trx);
}
await qb;
}
if ('joinTable' in attribute && attribute.joinTable) {
const { joinTable } = attribute;
if (joinTable.joinColumn) {
Object.assign(payload, {
[joinTable.joinColumn.name]: left.ref
});
}
const assignInverseColumn = ()=>{
if ('inverseJoinColumn' in joinTable && joinTable.inverseJoinColumn) {
Object.assign(payload, {
[joinTable.inverseJoinColumn.name]: right.ref
});
}
};
const assignOrderColumns = ()=>{
if ('orderColumnName' in joinTable && joinTable.orderColumnName) {
Object.assign(payload, {
[joinTable.orderColumnName]: left.pos ?? null
});
}
if ('inverseOrderColumnName' in joinTable && joinTable.inverseOrderColumnName) {
Object.assign(payload, {
[joinTable.inverseOrderColumnName]: right.pos ?? null
});
}
};
const assignMorphColumns = ()=>{
if ('morphColumn' in joinTable && joinTable.morphColumn) {
const { idColumn, typeColumn } = joinTable.morphColumn ?? {};
if (idColumn) {
Object.assign(payload, {
[idColumn.name]: right.ref
});
}
if (typeColumn) {
Object.assign(payload, {
[typeColumn.name]: right.type
});
}
Object.assign(payload, {
order: right.pos ?? null,
field: right.field ?? null
});
}
};
if (kind === 'relation.basic' || kind === 'relation.circular') {
assignInverseColumn();
}
if (kind === 'relation.morph') {
assignMorphColumns();
}
assignOrderColumns();
const qb = connection.insert(payload).into(addSchema(joinTable.name));
if (trx) {
await qb.transacting(trx);
}
}
if ('morphColumn' in attribute && attribute.morphColumn) {
const { morphColumn } = attribute;
const qb = connection(addSchema(metadata.tableName)).where('id', left.ref).update({
[morphColumn.idColumn.name]: right.ref,
[morphColumn.typeColumn.name]: right.type
});
if (trx) {
qb.transacting(trx);
}
await qb;
}
};
return {
generateAll,
generateAllForAttribute,
insert
};
};
return query;
};
const filterValidRelationalAttributes = (attributes)=>{
const isOwner = (attribute)=>{
return attribute.owner || !attribute.mappedBy && !attribute.morphBy;
};
const isComponentLike = (attribute)=>attribute.joinTable?.name.endsWith('_cmps');
return Object.entries(attributes).filter(([, attribute])=>{
return attribute.type === 'relation' && isOwner(attribute) && !isComponentLike(attribute);
}).reduce((acc, [key, attribute])=>({
...acc,
[key]: attribute
}), {});
};
const getLinkKind = (attribute, uid)=>{
if (attribute.relation.startsWith('morph')) {
return 'relation.morph';
}
if (attribute.target === uid) {
return 'relation.circular';
}
return 'relation.basic';
};
export { createLinkQuery, filterValidRelationalAttributes };
//# sourceMappingURL=link.mjs.map