1 | import * as fs from "fs";
|
2 | import * as request from "request";
|
3 | import * as retry from "retry";
|
4 |
|
5 | import { MagnoliaSourceOptions } from "./magnolia-source-options.interface";
|
6 | import { MagnoliaLoadingParams } from "./magnolia-loading-params.interface";
|
7 |
|
8 | export function fetchSitemap(
|
9 | options: MagnoliaSourceOptions
|
10 | ): Promise<string[]> {
|
11 | return loadDataFromMagnolia<string[]>({
|
12 | authHeader: options.magnolia.auth.header,
|
13 | url: options.magnolia.url + options.magnolia.sitemapEndpoint,
|
14 | errorMessage:
|
15 | "Attempt to get the sitemap failed, will retry in some time...",
|
16 | resolverHandler: body => body,
|
17 | retryOptions: { forever: true }
|
18 | });
|
19 | }
|
20 |
|
21 | export function fetchWorkspace(
|
22 | workspace: string,
|
23 | options: MagnoliaSourceOptions
|
24 | ): Promise<any> {
|
25 | return loadDataFromMagnolia<any>({
|
26 | authHeader: options.magnolia.auth.header,
|
27 | url: options.magnolia.url + "/.rest/delivery/" + workspace + "/v1",
|
28 | errorMessage: `Attempt to get workspace ${workspace} failed, will retry in some time...`,
|
29 | resolverHandler: body => body.results
|
30 | });
|
31 | }
|
32 |
|
33 | export function fetchPages(options: MagnoliaSourceOptions): Promise<any> {
|
34 | return loadDataFromMagnolia<any>({
|
35 | authHeader: options.magnolia.auth.header,
|
36 | url: options.magnolia.url + options.magnolia.pagesEndpoint,
|
37 | errorMessage: `Attempt to get pages failed, will retry in some time...`,
|
38 | resolverHandler: body => body.results
|
39 | });
|
40 | }
|
41 |
|
42 | function loadDataFromMagnolia<T>(params: MagnoliaLoadingParams<T>) {
|
43 | const operation = retry.operation(params.retryOptions);
|
44 | return new Promise<T>(resolve => {
|
45 | operation.attempt(() => {
|
46 | request.get(
|
47 | params.url,
|
48 | {
|
49 | json: true,
|
50 | headers: {
|
51 | Authorization: params.authHeader,
|
52 | "User-Agent": "Paperboy"
|
53 | },
|
54 | timeout: 60 * 1000
|
55 | },
|
56 | (error, response, body: T) => {
|
57 | const operationError =
|
58 | error ||
|
59 | (response && response.statusCode !== 200
|
60 | ? new Error(`Return code was: ${response.statusCode}`)
|
61 | : undefined);
|
62 |
|
63 | if (operation.retry(operationError)) {
|
64 | console.error(params.errorMessage);
|
65 |
|
66 | if (error) {
|
67 | console.error(error);
|
68 | }
|
69 |
|
70 | return;
|
71 | }
|
72 |
|
73 | resolve(params.resolverHandler(body));
|
74 | }
|
75 | );
|
76 | });
|
77 | });
|
78 | }
|
79 |
|
80 | export function writePagesFile(
|
81 | pages: any[],
|
82 | options?: MagnoliaSourceOptions
|
83 | ): Promise<void> {
|
84 | return new Promise((resolve, reject) => {
|
85 | if (!fs.existsSync(options.output.json)) {
|
86 | fs.mkdirSync(options.output.json);
|
87 | }
|
88 |
|
89 | fs.writeFile(
|
90 | options.output.json + "/pages.json",
|
91 | JSON.stringify(pages),
|
92 | err => {
|
93 | if (err) {
|
94 | reject(err);
|
95 | }
|
96 |
|
97 | resolve();
|
98 | }
|
99 | );
|
100 | });
|
101 | }
|
102 |
|
103 | export function writeWorkspaceFile(
|
104 | workspace: string,
|
105 | workspaceData: any,
|
106 | options?: MagnoliaSourceOptions
|
107 | ): Promise<void> {
|
108 | return new Promise((resolve, reject) => {
|
109 | if (!fs.existsSync(options.output.json)) {
|
110 | fs.mkdirSync(options.output.json);
|
111 | }
|
112 |
|
113 | fs.writeFile(
|
114 | options.output.json + "/" + workspace + ".json",
|
115 | JSON.stringify(workspaceData),
|
116 | err => {
|
117 | if (err) {
|
118 | reject(err);
|
119 | }
|
120 |
|
121 | resolve();
|
122 | }
|
123 | );
|
124 | });
|
125 | }
|
126 |
|
127 | export function sanitizeJson(
|
128 | json: any,
|
129 | damAssets: any[],
|
130 | pages: any,
|
131 | sourceOptions: MagnoliaSourceOptions,
|
132 | workspaces: { [workspace: string]: any } = {}
|
133 | ): any {
|
134 | const sanitized: any = {};
|
135 |
|
136 | if (json) {
|
137 | Object.keys(json).forEach(key => {
|
138 | const isKeyExcluded =
|
139 | sourceOptions &&
|
140 | sourceOptions.output.excludedProperties &&
|
141 | sourceOptions.output.excludedProperties.findIndex(
|
142 | prop => prop === key
|
143 | ) > -1;
|
144 |
|
145 | if (!isKeyExcluded && key === "@nodes") {
|
146 | const contentOrder: string[] = json[key];
|
147 |
|
148 | if (contentOrder.length > 0) {
|
149 | sanitized[key.substr(1)] = contentOrder.map(contentKeyIndex =>
|
150 | sanitizeJson(
|
151 | json[contentKeyIndex],
|
152 | damAssets,
|
153 | pages,
|
154 | sourceOptions,
|
155 | workspaces
|
156 | )
|
157 | );
|
158 | }
|
159 | } else if (!isKeyExcluded && key !== "content" && !key.match(/^\d+$/)) {
|
160 | const originalKey = key;
|
161 | const sanitizedKey = key
|
162 | .replace(/^@/, "")
|
163 | .replace(/^mgnl:/, "")
|
164 | .replace(/^jcr:uuid/, "id");
|
165 |
|
166 | if (!sanitizedKey.match(/^jcr:/)) {
|
167 | if (typeof json[key] === "object" && !Array.isArray(json[key])) {
|
168 | sanitized[sanitizedKey] = sanitizeJson(
|
169 | json[key],
|
170 | damAssets,
|
171 | pages,
|
172 | sourceOptions,
|
173 | workspaces
|
174 | );
|
175 | } else {
|
176 | let items = Array.isArray(json[key]) ? json[key] : [json[key]];
|
177 |
|
178 | items = items.map((item: any) => {
|
179 | if (
|
180 | item.match(
|
181 | /^jcr:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
182 | )
|
183 | ) {
|
184 | const uuid = item.replace("jcr:", "");
|
185 | return damAssets.find(
|
186 | damAsset => damAsset && damAsset.id === uuid
|
187 | );
|
188 | } else if (
|
189 | !originalKey.match(/^@/) &&
|
190 | !originalKey.match(/^jcr:uuid/) &&
|
191 | item.match(
|
192 | /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
193 | )
|
194 | ) {
|
195 | const node = getPopulatedNode(item, pages);
|
196 | let value: any;
|
197 |
|
198 | if (node) {
|
199 | value = Object.assign(node || {}, {
|
200 | workspace: "website"
|
201 | });
|
202 | } else {
|
203 | const asset = getPopulatedNode(item, damAssets);
|
204 |
|
205 | if (asset) {
|
206 | value = Object.assign(asset || {}, {
|
207 | workspace: "dam"
|
208 | });
|
209 | } else {
|
210 | value = {};
|
211 | Object.keys(workspaces)
|
212 | .forEach(key => {
|
213 | const foundItem = getPopulatedNode(item, workspaces[key]);
|
214 | if (foundItem) {
|
215 | value = Object.assign({}, foundItem, { workspace: key });
|
216 | }
|
217 | });
|
218 | }
|
219 | }
|
220 |
|
221 | return value;
|
222 | } else {
|
223 | return item;
|
224 | }
|
225 | });
|
226 |
|
227 | sanitized[sanitizedKey] = Array.isArray(json[key])
|
228 | ? items
|
229 | : items[0];
|
230 | }
|
231 | }
|
232 | }
|
233 | });
|
234 | }
|
235 |
|
236 | return sanitized;
|
237 | }
|
238 |
|
239 | export function getPopulatedNode(
|
240 | id: string,
|
241 | source: any,
|
242 | populatedNode?: any
|
243 | ): any {
|
244 | if (populatedNode || !source) {
|
245 | return populatedNode;
|
246 | } else {
|
247 | if (source["jcr:uuid"] === id || source.id === id) {
|
248 | populatedNode = {
|
249 | id,
|
250 | path: source["@path"] || source.path
|
251 | };
|
252 | } else {
|
253 | Object.keys(source).forEach(key => {
|
254 | if (source[key] && typeof source[key] === "object") {
|
255 | populatedNode = getPopulatedNode(id, source[key], populatedNode);
|
256 | }
|
257 | });
|
258 | }
|
259 |
|
260 | return populatedNode;
|
261 | }
|
262 | }
|