2022-01-27 12:41:28 +01:00
|
|
|
import { Directus } from '@directus/sdk'
|
|
|
|
import yaml from 'js-yaml'
|
|
|
|
import fs from 'fs'
|
|
|
|
import Axios from 'axios'
|
2022-02-23 16:14:51 +01:00
|
|
|
import path from 'path'
|
2022-01-27 12:41:28 +01:00
|
|
|
|
|
|
|
export default class DirectusToMarkdown {
|
|
|
|
constructor(config) {
|
|
|
|
this.url = config.url || process.env.DIRECTUS_URL
|
|
|
|
this.token = config.token || process.env.DIRECTUS_TOKEN
|
|
|
|
this.contentKey = config.contentKey || 'content'
|
|
|
|
this.collections = config.collections
|
|
|
|
this.directus = new Directus(this.url, { auth: { staticToken: this.token }});
|
|
|
|
}
|
|
|
|
|
2022-02-23 16:13:25 +01:00
|
|
|
_formatFrontMatter(item, deleteFields) {
|
2022-01-27 12:41:28 +01:00
|
|
|
const front = { ...item } // copie item
|
|
|
|
delete front[this.contentKey]
|
2022-02-23 16:13:25 +01:00
|
|
|
for (const field of deleteFields) {
|
|
|
|
delete front[field]
|
|
|
|
}
|
2022-01-27 12:41:28 +01:00
|
|
|
return `---\r\n${yaml.dump(front).trim()}\r\n---\r\n\r\n`
|
|
|
|
}
|
|
|
|
|
2022-02-23 16:13:25 +01:00
|
|
|
async _writeIndex(item, itemPath, deleteFields) {
|
|
|
|
const frontMatter = this._formatFrontMatter(item, deleteFields)
|
2022-01-27 12:41:28 +01:00
|
|
|
const content = item[this.contentKey] ? item[this.contentKey].toString() : ''
|
2022-01-28 00:18:00 +01:00
|
|
|
const itemContent = `${frontMatter}${content}\r\n`
|
2022-02-23 16:14:51 +01:00
|
|
|
const filePath = `${itemPath}/${item.path ? item.path : 'index.md'}`
|
|
|
|
const fileDirname = path.dirname(filePath)
|
|
|
|
if (!fs.existsSync(fileDirname)) {
|
|
|
|
fs.mkdirSync(fileDirname, { recursive: true });
|
|
|
|
}
|
|
|
|
fs.writeFileSync(filePath, itemContent)
|
2022-01-27 12:41:28 +01:00
|
|
|
}
|
|
|
|
|
2022-02-23 16:14:51 +01:00
|
|
|
async _writeFile(response, filePath) {
|
|
|
|
const writer = fs.createWriteStream(filePath)
|
2022-01-27 12:41:28 +01:00
|
|
|
|
|
|
|
response.data.pipe(writer)
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
writer.on('finish', resolve)
|
|
|
|
writer.on('error', reject)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async _downloadAssets(item, itemPath) {
|
|
|
|
const uuidregex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
|
|
for (const [key, uuid] of Object.entries(item)){
|
|
|
|
// TODO: instead of trying to pull asset and hope for the best,
|
|
|
|
// crosscheck things with the /fields and play by the rules
|
|
|
|
if (typeof uuid === 'string' && uuid.match(uuidregex)) {
|
|
|
|
const filename = await this._downloadAsset(uuid, itemPath)
|
|
|
|
item[key] = filename // Update field with filename instead of uuid
|
2022-04-29 12:15:43 +02:00
|
|
|
} else if (Array.isArray(uuid)) {
|
|
|
|
for (const element of uuid) {
|
|
|
|
await this._downloadAssets(element, itemPath)
|
|
|
|
}
|
2022-01-27 12:41:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async _downloadAsset(uuid, itemPath) {
|
|
|
|
const response = await Axios({url: `${this.url}/assets/${uuid}?download&access_token=${this.token}`, method: 'GET', responseType: 'stream'})
|
|
|
|
const disposition = response.headers['content-disposition'].match(/filename="(.*)"/);
|
|
|
|
const filename = disposition ? disposition[1] : `${value}.${mime.extension(response.headers['content-type'])}`;
|
|
|
|
const savePath = `${itemPath}/${filename}`;
|
|
|
|
await this._writeFile(response, savePath)
|
|
|
|
return filename
|
|
|
|
}
|
|
|
|
|
|
|
|
async _downloadAssetsFromContent(item, itemPath) {
|
|
|
|
const uuidregex = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
|
|
|
|
const regEx = new RegExp(`!\\[.*\\]\\(${this.url}/assets/(${uuidregex})\\)`, "ig")
|
|
|
|
const uuids = []
|
|
|
|
let asset
|
|
|
|
do {
|
|
|
|
asset = regEx.exec(item[this.contentKey])
|
|
|
|
if (asset) uuids.push(asset[1])
|
|
|
|
} while (asset)
|
|
|
|
|
|
|
|
for (const uuid of uuids) {
|
|
|
|
const filename = await this._downloadAsset(uuid, itemPath)
|
|
|
|
const url = `${this.url}/assets/${uuid}`
|
|
|
|
item[this.contentKey] = item[this.contentKey].replace(url, filename)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async export() {
|
|
|
|
for (const collectionName in this.collections) {
|
|
|
|
const collection = this.collections[collectionName]
|
2022-02-21 17:33:50 +01:00
|
|
|
const readByQueryOption = collection.readByQueryOption
|
2022-02-23 16:13:25 +01:00
|
|
|
const deleteFields = collection.deleteFields
|
2022-02-21 17:33:50 +01:00
|
|
|
const items = (await this.directus.items(collectionName).readByQuery(readByQueryOption)).data
|
2022-01-27 12:41:28 +01:00
|
|
|
for (const item of items) {
|
|
|
|
const itemPath = collection.pathBuilder(item)
|
|
|
|
if (!fs.existsSync(itemPath)) {
|
|
|
|
fs.mkdirSync(itemPath, { recursive: true });
|
|
|
|
}
|
|
|
|
await this._downloadAssets(item, itemPath)
|
|
|
|
await this._downloadAssetsFromContent(item, itemPath)
|
2022-02-23 16:13:25 +01:00
|
|
|
await this._writeIndex(item, itemPath, deleteFields)
|
2022-01-27 12:41:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|