Created
October 13, 2023 08:43
-
-
Save timofei-iatsenko/eff1611f7957360be400c9ad23a83330 to your computer and use it in GitHub Desktop.
Migration script from payload-lexical-plugin to Payload 2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-mongodb'; | |
import { Field, RichTextField, BlockField } from 'payload/dist/fields/config/types'; | |
import { CollectionModel } from '@payloadcms/db-mongodb/dist/types'; | |
/** | |
* Migration steps: | |
* 1. Go and change all your fields to `richText` instead of plugin's wrapper | |
* 2. Run a migration | |
* | |
* This migration supports: | |
* - Localized and not localized fields | |
* - Root fields in collection & globals | |
* - Recursively searhing for fields in blocks if any defined | |
* - Versions | |
*/ | |
export class RethrownError extends Error { | |
public message: string; | |
constructor(message: string, originalError: Error) { | |
super(); | |
this.message = message + ' ' + originalError.message; | |
this.stack = `Error: ${message} \nOriginal: ` + originalError.stack; | |
} | |
} | |
function migrateLocalizedField(fieldValue: any) { | |
return Object.keys(fieldValue).reduce((acc, lang) => { | |
acc[lang] = fieldValue[lang].jsonContent || fieldValue[lang]; | |
return acc; | |
}, {}); | |
} | |
function migrateField(fieldValue: any) { | |
return fieldValue?.jsonContent || fieldValue; | |
} | |
function appendPath(prefix: string, ...value: string[]) { | |
return [...prefix.split('.'), ...value].filter(Boolean).join('.'); | |
} | |
function updateDoc( | |
doc: Record<string, any>, | |
fields: Field[], | |
path: string = '' | |
): Record<string, any> { | |
// update root fields | |
const richTextFields = fields.filter((field) => field.type === 'richText') as RichTextField[]; | |
let update: Record<string, any> = {}; | |
richTextFields.forEach((field) => { | |
try { | |
const backupFieldName = '__old__' + field.name; | |
// if already processed (has a backup) skip or if empty | |
if (doc[backupFieldName] || !doc[field.name]) { | |
return; | |
} | |
update[appendPath(path, field.name)] = field.localized | |
? migrateLocalizedField(doc[field.name]) | |
: migrateField(doc[field.name]); | |
// store a backup | |
update[appendPath(path, backupFieldName)] = doc[field.name]; | |
} catch (e) { | |
throw new RethrownError(`Error while processing ${appendPath(path, field.name)}`, e); | |
} | |
}); | |
// update blocks if any | |
const blockFields = fields.filter((field) => field.type === 'blocks') as BlockField[]; | |
blockFields.forEach((field) => { | |
if (!doc[field.name]) { | |
return; | |
} | |
function updateBlock(blockDoc: any, blockPath: string) { | |
const blockDef = field.blocks.find((block) => block.slug === blockDoc.blockType); | |
if (!blockDef) { | |
throw new Error( | |
`found unsupported block of type ${blockDoc.blockType} in path: ${blockPath}, id: ${doc._id}` | |
); | |
} | |
update = { | |
...update, | |
...updateDoc(blockDoc, blockDef.fields, blockPath), | |
}; | |
} | |
if (Array.isArray(doc[field.name])) { | |
doc[field.name].forEach((blockDoc, index) => { | |
updateBlock(blockDoc, appendPath(path, field.name, index.toString())); | |
}); | |
} else { | |
updateBlock(doc[field.name], appendPath(path, field.name)); | |
} | |
}); | |
return update; | |
} | |
async function writeDoc(_id: any, collection: CollectionModel['collection'], update: any) { | |
if (!Object.keys(update).length) { | |
return; | |
} | |
await collection.findOneAndUpdate( | |
{ | |
_id, | |
}, | |
{ | |
$set: update, | |
} | |
); | |
} | |
async function updateAndWriteVersionDoc( | |
doc: Record<string, any>, | |
fields: Field[], | |
collection: CollectionModel['collection'] | |
) { | |
try { | |
const update = updateDoc(doc.version, fields, 'version'); | |
await writeDoc(doc._id, collection, update); | |
} catch (e) { | |
throw new RethrownError( | |
`Error processing doc id: ${doc._id}, collection: ${collection.name}`, | |
e | |
); | |
} | |
} | |
async function updateAndWriteDoc( | |
doc: Record<string, any>, | |
fields: Field[], | |
collection: CollectionModel['collection'] | |
) { | |
try { | |
const update = updateDoc(doc, fields); | |
await writeDoc(doc._id, collection, update); | |
} catch (e) { | |
throw new RethrownError( | |
`Error processing doc id: ${doc._id}, collection: ${collection.name}`, | |
e | |
); | |
} | |
} | |
export async function up({ payload }: MigrateUpArgs): Promise<void> { | |
// For each collection | |
await Promise.all( | |
payload.config.collections.map(async ({ slug, fields, versions }) => { | |
const Collection = payload.db.collections[slug].collection; | |
const docs = await Collection.find().toArray(); | |
await Promise.all(docs.map((doc) => updateAndWriteDoc(doc, fields, Collection))); | |
// for each version of collection | |
if (versions) { | |
const Collection = payload.db.versions[slug].collection; | |
const docs = await Collection.find().toArray(); | |
return Promise.all(docs.map((doc) => updateAndWriteVersionDoc(doc, fields, Collection))); | |
} | |
}) | |
); | |
// For each global | |
const Globals = payload.db.globals.collection; | |
const docs = await Globals.find().toArray(); | |
await Promise.all( | |
docs.map((doc) => { | |
const globalDef = payload.config.globals.find((item) => item.slug === doc.globalType); | |
return updateAndWriteDoc(doc, globalDef.fields, Globals); | |
}) | |
); | |
// for each global versions | |
await Promise.all( | |
payload.config.globals.map(async ({ slug, fields, versions }) => { | |
if (versions) { | |
const VersionCollection = payload.db.versions[slug].collection; | |
const docs = await VersionCollection.find().toArray(); | |
return Promise.all( | |
docs.map((doc) => updateAndWriteVersionDoc(doc, fields, VersionCollection)) | |
); | |
} | |
}) | |
); | |
} | |
export async function down({ payload }: MigrateDownArgs): Promise<void> { | |
// Migration code | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment