Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
feat: prepares core functionality for importing data (ordering, extra…
Browse files Browse the repository at this point in the history
…cting depds..)
  • Loading branch information
Enngage committed Jan 9, 2020
1 parent ac2d156 commit ddebf52
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 44 deletions.
Binary file modified output/test.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion src/clean/clean.models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IProcessedItem } from '../models';
import { IProcessedItem } from '../core';

export interface ICleanConfig {
projectId: string;
Expand Down
2 changes: 1 addition & 1 deletion src/clean/clean.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IManagementClient, ManagementClient } from '@kentico/kontent-management';

import { ICleanConfig, ICleanResult } from './clean.models';
import { ItemType } from '../models';
import { ItemType } from '../core';

export class CleanService {
private readonly client: IManagementClient;
Expand Down
13 changes: 3 additions & 10 deletions src/cli/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import JSZip = require('jszip');
import yargs = require('yargs');

import { ExportService } from '../export';
import { CliAction } from '../models';
import { CliAction } from '../core';
import { ImportService } from '../import';
import { CleanService } from '../clean';

Expand Down Expand Up @@ -34,13 +34,7 @@ if (action.toLowerCase() === 'backup') {
throw Error(`Unsupported action type '${action}'.`);
}

// tslint:disable-next-line: max-line-length
const sourceApiKey: string = '';
const sourceProjectId: string = 'b259760f-81c5-013a-05e7-69efb4b954e5';

// tslint:disable-next-line: max-line-length
const targetApiKey: string = '';
const targetProjectId: string = 'c0135fb2-af2f-01be-c387-d0c762c23301';

const exportService = new ExportService({
apiKey: sourceApiKey,
Expand All @@ -52,7 +46,6 @@ const importService = new ImportService({
console.log('imported item: ' + item.title);
},
projectId: targetProjectId,
// tslint:disable-next-line: max-line-length
apiKey: targetApiKey
});

Expand All @@ -61,7 +54,6 @@ const cleanService = new CleanService({
console.log('deleted item: ' + item.title);
},
projectId: targetProjectId,
// tslint:disable-next-line: max-line-length
apiKey: targetApiKey
});

Expand All @@ -87,14 +79,15 @@ const backup = async () => {
});
});

await importService.importFromExportDataAsync(response.data);
};

const clean = async () => {
await cleanService.cleanAllAsync();
};

const restore = async () => {
const response = await exportService.exportAllAsync();
await importService.importFromExportDataAsync(response.data);
};

if (mappedAction === 'backup') {
Expand Down
88 changes: 88 additions & 0 deletions src/core/codename-translate-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
export class CodenameTranslateHelper {
public replaceIdReferencesWithCodenames(data: any, allData: any): void {
if (data) {
if (Array.isArray(data)) {
for (const arrayItem of data) {
this.replaceIdReferencesWithCodenames(arrayItem, allData);
}
} else {
for (const key of Object.keys(data)) {
const val = (data as any)[key];
if (key.toLowerCase() === 'id') {
const id = (data as any).id;
const codename = (data as any).codename;

if (!codename) {
// replace id with codename
const foundCodename = this.tryFindCodenameForId(id, allData);

if (foundCodename) {
// remove id prop
delete data.id;

// set codename prop
data.codename = foundCodename;
}
}
}
if (key !== '0') {
this.replaceIdReferencesWithCodenames(val, allData);
}
}
}
}
}

public extractReferencedCodenames(data: any, allData: any, foundCodenames: string[]): void {
if (data) {
if (Array.isArray(data)) {
for (const arrayItem of data) {
this.extractReferencedCodenames(arrayItem, allData, foundCodenames);
}
} else {
for (const key of Object.keys(data)) {
const val = (data as any)[key];
if (key.toLowerCase() === 'codename') {
const id = (data as any).id;
const codename = (data as any).codename;

if (codename && !id) {
foundCodenames.push(codename);
}
}
if (key !== '0') {
this.extractReferencedCodenames(val, allData, foundCodenames);
}
}
}
}
}

public tryFindCodenameForId(findId: string, data: any, foundCodename?: string): string | undefined {
if (data) {
if (Array.isArray(data)) {
for (const arrayItem of data) {
foundCodename = this.tryFindCodenameForId(findId, arrayItem, foundCodename);
}
} else {
for (const key of Object.keys(data)) {
const val = (data as any)[key];
if (key.toLowerCase() === 'id') {
const id = (data as any).id;
const codename = (data as any).codename;

if (codename && id === findId) {
return codename;
}
}
if (key !== '0') {
foundCodename = this.tryFindCodenameForId(findId, val, foundCodename);
}
}
}
}
return foundCodename;
}
}

export const codenameTranslateHelper = new CodenameTranslateHelper();
14 changes: 14 additions & 0 deletions src/core/core.models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ContentTypeModels, ContentTypeSnippetModels, TaxonomyModels } from '@kentico/kontent-management';

export type CliAction = 'backup' | 'restore' | 'clean';
export type ItemType = 'taxonomy' | 'contentType' | 'contentTypeSnippet';
export type ValidImportType =
| ContentTypeModels.ContentType
| TaxonomyModels.Taxonomy
| ContentTypeSnippetModels.ContentTypeSnippet;

export interface IProcessedItem {
title: string;
type: ItemType;
data: any;
}
2 changes: 2 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './core.models';
export * from './codename-translate-helper';
14 changes: 8 additions & 6 deletions src/export/export.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
TaxonomyContracts,
} from '@kentico/kontent-management';

import { IExportAllResult, IExportConfig } from './export.models';
import { IExportAllResult, IExportConfig, IExportData } from './export.models';

export class ExportService {
private readonly client: IManagementClient;
Expand All @@ -19,16 +19,18 @@ export class ExportService {
}

public async exportAllAsync(): Promise<IExportAllResult> {
const data: IExportData = {
contentTypes: await this.exportContentTypesAsync(),
contentTypeSnippets: await this.exportContentTypeSnippetsAsync(),
taxonomies: await this.exportTaxonomiesAsync()
};

return {
metadata: {
timestamp: new Date(),
projectId: this.config.projectId
},
data: {
contentTypes: await this.exportContentTypesAsync(),
contentTypeSnippets: await this.exportContentTypeSnippetsAsync(),
taxonomies: await this.exportTaxonomiesAsync()
}
data
};
}

Expand Down
88 changes: 88 additions & 0 deletions src/import/import.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { codenameTranslateHelper } from '../core';
import { IExportData } from '../export';
import { IImportData, IPreparedImportItem } from './import.models';

export class ImportHelper {
public prepareImportData(exportData: IExportData): IImportData {
// translate internal ids to codenames
codenameTranslateHelper.replaceIdReferencesWithCodenames(exportData, exportData);

// flatten data
const items = this.flattenExportData(exportData);

// order items so that they can be imported (e.g. first import item and then item that depends on it)
this.orderItemsByDeps(items);

return {
orderedImportItems: items
};
}

private orderItemsByDeps(items: IPreparedImportItem[]): IPreparedImportItem[] {
for (const item of items) {
// set deps of item
item.deps.push(...this.getDependenciesOfItem(item, items));
}

const sortedItems = items.sort((a, b) => {
if (a.codename === b.codename) {
return 0;
}

// order items so that dependent items are first
if (a.deps.includes(b.codename)) {
return 1;
} else {
return -1;
}
});

return sortedItems;
}

private getDependenciesOfItem(
item: IPreparedImportItem,
allItems: IPreparedImportItem[]
): string[] {
const deps: string[] = [];

// get referenced codenames in item
codenameTranslateHelper.extractReferencedCodenames(item, allItems, deps);

// filter codename of the item itself
const filteredDeps = deps.filter(m => m !== item.codename);

return filteredDeps;
}

private flattenExportData(exportData: IExportData): IPreparedImportItem[] {
return [
...exportData.taxonomies.map(m => {
return <IPreparedImportItem> {
codename: m.codename,
deps: [],
item: m,
type: 'taxonomy'
};
}),
...exportData.contentTypeSnippets.map(m => {
return <IPreparedImportItem> {
codename: m.codename,
deps: [],
item: m,
type: 'contentTypeSnippet'
};
}),
...exportData.contentTypes.map(m => {
return <IPreparedImportItem> {
codename: m.codename,
deps: [],
item: m,
type: 'contentType'
};
})
];
}
}

export const importHelper = new ImportHelper();
17 changes: 14 additions & 3 deletions src/import/import.models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IProcessedItem } from '../models';
import { IProcessedItem, ItemType } from '../core';

export interface IImportConfig {
projectId: string;
Expand All @@ -8,7 +8,18 @@ export interface IImportConfig {

export interface IImportAllResult {
metadata: {
timestamp: Date,
projectId: string
timestamp: Date;
projectId: string;
};
}

export interface IPreparedImportItem {
type: ItemType;
codename: string;
item: any;
deps: string[];
}

export interface IImportData {
orderedImportItems: IPreparedImportItem[];
}
43 changes: 30 additions & 13 deletions src/import/import.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
TaxonomyModels,
} from '@kentico/kontent-management';

import { ItemType, ValidImportType } from '../core';
import { IExportData } from '../export';
import { IImportAllResult, IImportConfig } from './import.models';
import { ItemType } from 'src/models';
import { importHelper } from './import.helper';
import { IImportConfig, IImportData, IPreparedImportItem } from './import.models';

export class ImportService {
private readonly client: IManagementClient;
Expand All @@ -23,17 +24,33 @@ export class ImportService {
});
}

public async importFromExportDataAsync(exportData: IExportData): Promise<IImportAllResult> {
await this.importTaxonomiesAsync(exportData.taxonomies);
await this.importContentTypeSnippetsAsync(exportData.contentTypeSnippets);
await this.importContentTypesAsync(exportData.contentTypes);

return {
metadata: {
projectId: this.config.projectId,
timestamp: new Date()
}
};
public async importFromExportDataAsync(exportData: IExportData): Promise<ValidImportType[]> {
const importData = importHelper.prepareImportData(exportData);

return await this.importAsync(importData);
}

public async importAsync(importData: IImportData): Promise<ValidImportType[]> {
const importedItems: ValidImportType[] = [];

for (const item of importData.orderedImportItems) {
const importedItem = await this.importItemAsync(item);
importedItems.push(...importedItem);
}

return importedItems;
}

public async importItemAsync(item: IPreparedImportItem): Promise<ValidImportType[]> {
if (item.type === 'contentType') {
return await this.importContentTypesAsync([item.item]);
} else if (item.type === 'taxonomy') {
return await this.importTaxonomiesAsync([item.item]);
} else if (item.type === 'contentTypeSnippet') {
return await this.importContentTypeSnippetsAsync([item.item]);
} else {
throw Error(`Not supported import data type '${item.type}'`);
}
}

public async importContentTypesAsync(
Expand Down
1 change: 1 addition & 0 deletions src/import/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './import.models';
export * from './import.service';
export * from './import.helper';
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Public API
export * from './models';
export * from './core';
export * from './export';
export * from './import';
export * from './clean';
Loading

0 comments on commit ddebf52

Please sign in to comment.