Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new import/export comand #92

Merged
merged 33 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3794938
new import/export command
KarolNet Nov 25, 2024
2bb113f
new import/export command - test
KarolNet Nov 26, 2024
bd0dd9c
new import/export command - test
KarolNet Nov 26, 2024
188ebc2
new import/export command - remove unused dep
KarolNet Nov 26, 2024
1faa8dd
new import/export command - import simple test
KarolNet Nov 27, 2024
4a21ea3
new import/export command - refactor
KarolNet Nov 27, 2024
f9540aa
new import/export command - fix disable webhook option bug
KarolNet Nov 28, 2024
2374acf
new import/export command - disable webhooks optimization
KarolNet Nov 28, 2024
3a9d63e
new import/export command - refactor
KarolNet Nov 28, 2024
aa74106
new import/export command - update readme
KarolNet Nov 28, 2024
a88e3a3
new import/export command - version up
KarolNet Nov 28, 2024
0cc555d
new import/export command - fix --only-definitions option
KarolNet Dec 4, 2024
27c476e
new import/export command - skip import featuredImage for ctd, disabl…
KarolNet Dec 10, 2024
0100247
new import/export command -
KarolNet Dec 10, 2024
6ba9832
new import/export command -
KarolNet Jan 10, 2025
14b8f5c
fix names clash
CiotkaCierpienia Jan 13, 2025
de0df65
new import/export command
KarolNet Nov 25, 2024
5fb3aa4
new import/export command - test
KarolNet Nov 26, 2024
50e5a67
new import/export command - test
KarolNet Nov 26, 2024
1e8844c
new import/export command - remove unused dep
KarolNet Nov 26, 2024
925ebb6
new import/export command - import simple test
KarolNet Nov 27, 2024
d2a1760
new import/export command - refactor
KarolNet Nov 27, 2024
8aac7cf
new import/export command - fix disable webhook option bug
KarolNet Nov 28, 2024
9758e10
new import/export command - disable webhooks optimization
KarolNet Nov 28, 2024
cd3b34e
new import/export command - refactor
KarolNet Nov 28, 2024
fda28e6
new import/export command - update readme
KarolNet Nov 28, 2024
5c9a22e
new import/export command - version up
KarolNet Nov 28, 2024
cc88d2f
new import/export command - fix --only-definitions option
KarolNet Dec 4, 2024
efb0570
new import/export command - skip import featuredImage for ctd, disabl…
KarolNet Dec 10, 2024
205ea3e
new import/export command -
KarolNet Dec 10, 2024
2649a24
new import/export command -
KarolNet Jan 10, 2025
b66c126
fix names clash
CiotkaCierpienia Jan 13, 2025
d4b29c2
Merge remote-tracking branch 'origin/new-importer-exporter' into new-…
CiotkaCierpienia Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
name: Tests

on:
push:
branches:
- main
- feature/export
on: [push]

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install deps
run: npm install
- name: Jast run
run: npm test
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- run: yarn install
- run: yarn test
9 changes: 1 addition & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ This command exports data from the Flotiq account to local JSON files. If the ke
`flotiq import [projectName] [flotiqApiKey]`

This command imports Content Types and Content Objects to your Flotiq account using the API key.
Source directory must include directory with `ContentType[0-9]` folders, each of them containing ContentTypeDefinition.json file, and `contentObject[0-9].json` files.

The number at the end of the directory or file name defines the file import order.
The `./images` directory in a particular starter stores images that will be imported into your Media Library.
Source directory must include directory with `ContentType[Name]` folders, each of them containing ContentTypeDefinition.json file, and `contentObject[Name].json` files.

The command can import data output of the `flotiq export` command.

Expand All @@ -65,10 +62,6 @@ The command can import data output of the `flotiq export` command.
* `projectName` - project name or project path (if you wish to start or import data from the directory you are in, use `.`)
* `flotiqApiKey` - read and write API key to your Flotiq account

#### Note

It is also possible to perform the import and export manually, using the Flotiq API without relying on the CLI importer. However, when doing this, you need to ensure that image URLs are updated, as images will receive new IDs during the migration process. This requires careful handling to avoid broken links in your Content Objects. For more details on how to properly handle this, refer to the [Flotiq API documentation](https://flotiq.com/docs/).

### Launch a Flotiq starter project

`flotiq start [projectName] [flotiqStarterUrl] [flotiqApiKey]`
Expand Down
177 changes: 177 additions & 0 deletions commands/exporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env node

const fs = require("fs/promises");
const path = require("path");
const fetch = require("node-fetch");
const logger = require("./../src/logger");
const { camelize } = require("./../src/util");
const FlotiqApi = require('./../src/flotiq-api')
const config = require("../src/configuration/config");

exports.command = "export";
exports.description = "Export flotiq entities to JSON structure";
exports.builder = {
target: {
description: "Export directory",
alias: "directory",
type: "string",
demand: true,
},
ctd: {
description: "Coma-delimited list of CTD to export",
type: "string",
},
skipContent: {
description: "Dump only CTD"
}
};

async function exporter(directory, flotiqApiUrl, flotiqApiKey, skipContent, ctd) {
try {
const files = await fs.readdir(directory);

if (files.length > 0) {
logger.error(`${directory} exists, but isn't empty`);
return false;
}
} catch (e) {
// Skip
}

await fs.mkdir(directory, { recursive: true });

const flotiqApi = new FlotiqApi(flotiqApiUrl, flotiqApiKey);

let ContentTypeDefinitions = await flotiqApi.fetchContentTypeDefs();

if (ctd) {
ctd.split(",").forEach((c) => {
if (!ContentTypeDefinitions.map((d) => d.name).includes(c)) {
throw new Error(`Invalid ctd "${c}"`);
}
});
ContentTypeDefinitions = ContentTypeDefinitions.filter((def) =>
ctd.split(",").includes(def.name)
);
}

if (ContentTypeDefinitions.length === 0) {
logger.info("Nothing to do");
return true;
}

for (const contentTypeDefinition of ContentTypeDefinitions) {
logger.info(`Saving CTD for ${contentTypeDefinition.label}`);

const ctdPath = path.join(
directory,
`${contentTypeDefinition.internal ? 'Internal' : ''}ContentType${camelize(contentTypeDefinition.name)}`
);

await fs.mkdir(ctdPath, { recursive: true });

const contentTypeDefinitionToPersist = Object.fromEntries(
Object.entries(contentTypeDefinition).filter(([key]) => {
return ![
"id",
// "internal",
"deletedAt",
"createdAt",
"updatedAt",
].includes(key);
})
);

await fs.writeFile(
path.join(ctdPath, "ContentTypeDefinition.json"),
JSON.stringify(contentTypeDefinitionToPersist, null, 2)
);

if (!skipContent) {

const ContentObjects = await flotiqApi.fetchContentObjects(contentTypeDefinition.name);

if (ContentObjects.length === 0) {
logger.info(`No content to save for ${contentTypeDefinition.label}`);
continue;
}

logger.info(`Saving content for ${contentTypeDefinition.label}`);

await fs.writeFile(
path.join(
ctdPath,
`contentObject${camelize(contentTypeDefinition.name)}.json`
),
ContentObjects
.map((obj) => ({ ...obj, internal: undefined }))
.sort((a, b) => a.id < b.id ? -1 : 1)
.map(JSON.stringify).join("\n")
);

if (contentTypeDefinition.name === '_media') {
for (const mediaFile of ContentObjects) {
const outputPath = path.join(
ctdPath,
`${mediaFile.id}.${mediaFile.extension}`
);

const url = new URL(flotiqApiUrl);

await fetch(`${url.origin}${mediaFile.url}`)
.then(x => x.arrayBuffer())
.then(x => fs.writeFile(outputPath, Buffer.from(x)));
}
}
}
}
return true;
}

async function handler(argv) {

const dirStat = await fs.lstat(argv.directory);

if (!dirStat.isDirectory()) {
logger.error(`${argv.directory} exists, but isn't directory`);
return false;
}

await exporter(
argv.directory,
`${config.apiUrl}/api/v1`,
argv.flotiqApiKey,
argv['only-definitions']
)
}

module.exports = {
command: 'export [directory] [flotiqApiKey]',
describe: 'Export objects from Flotiq to directory',
builder: (yargs) => {
return yargs
.option("directory", {
description: "Directory path to import data.",
alias: "",
type: "string",
default: "",
demandOption: false,
})
.option("flotiqApiKey", {
description: "Flotiq Read and write API KEY.",
alias: "",
type: "string",
default: false,
demandOption: false,
})
.option("only-definitions", {
CiotkaCierpienia marked this conversation as resolved.
Show resolved Hide resolved
description: "Export only content type definitions, ignore content objects",
alias: "",
type: "boolean",
default: false,
demandOption: false,
})
},
handler,
exporter
}
Loading
Loading