-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from openearth/sync-viewer-layer
Sync viewer layer
- Loading branch information
Showing
6 changed files
with
480 additions
and
4 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
import { Geonetwork } from '../lib/geonetwork' | ||
import { datocmsRequest } from '../lib/datocms' | ||
import { addThumbnailsToRecord } from '../lib/add-thumbnails-to-record' | ||
import { withServerDefaults } from '../lib/with-server-defaults' | ||
import { buildMenuTree } from '../lib/build-menu-tree' | ||
import { findGeonetworkInstances } from '../lib/find-geonetwork-instances' | ||
import { fetchViewerLayerXML } from '../lib/fetch-viewer-layer-xml' | ||
import { formatMenusRecursive } from '../lib/format-menu' | ||
import Mailjet from 'node-mailjet' | ||
import fs from 'fs'; | ||
|
||
const mailjet = new Mailjet({ | ||
apiKey: process.env.MAILJET_API_TOKEN, | ||
apiSecret: process.env.MAILJET_API_SECRET, | ||
}) | ||
|
||
const viewersWithViewerLayersQuery = /* graphql */ ` | ||
query viewersWithViewerLayersQuery ($first: IntType, $skip: IntType = 0, $locale: SiteLocale = nl) { | ||
menus: allMenus(first: $first, skip: $skip, locale: $locale) { | ||
id | ||
geonetwork { | ||
baseUrl | ||
username | ||
password | ||
} | ||
errorNotificationContacts { | ||
} | ||
children: viewerLayers { | ||
id | ||
} | ||
parent { | ||
id | ||
} | ||
} | ||
_allMenusMeta { | ||
count | ||
} | ||
}` | ||
|
||
const viewerLayerByIdQuery = /* graphql */ ` | ||
query LayerById($id: ItemId) { | ||
viewerLayer(filter: {id: {eq: $id}}) { | ||
layer { | ||
thumbnails { | ||
filename | ||
url | ||
} | ||
} | ||
} | ||
}` | ||
|
||
export const handler = withServerDefaults(async (event, _) => { | ||
/* Protect this endpoint by using a token */ | ||
if (process.env.SYNC_LAYER_API_TOKEN !== event.headers['x-api-key']) { | ||
return { | ||
statusCode: 401, | ||
} | ||
} | ||
|
||
const data = JSON.parse(event.body) | ||
|
||
const id = data.entity.id | ||
|
||
const { menus } = await datocmsRequest({ | ||
query: viewersWithViewerLayersQuery, | ||
preview: true | ||
}) | ||
const formattedMenus = formatMenusRecursive(menus) | ||
const menuTree = buildMenuTree(formattedMenus) | ||
|
||
try { | ||
const type = data.related_entities.find(entity => entity.type === 'item_type').attributes.api_key | ||
|
||
if (type === 'viewer_layer') { | ||
await syncViewerLayers(menuTree, data.event_type, id) | ||
} else if (type === 'menu') { | ||
await syncViewer(menuTree, data.event_type, id) | ||
} | ||
} | ||
catch (e) { | ||
console.log('The following error occured', e.message) | ||
|
||
for (let email of findEmailContactsForLayerId(menuTree, layerId)) { | ||
console.log('Sending email to', email) | ||
|
||
await mailjet.post('send', { version: 'v3.1' }).request({ | ||
Messages: [ | ||
{ | ||
From: { | ||
Email: process.env.MAILJET_FROM_EMAIL, | ||
}, | ||
To: [ | ||
{ | ||
Email: email, | ||
}, | ||
], | ||
Subject: `Fout bij opslaan metadata voor laag ${layerId}`, | ||
HTMLPart: e.message, | ||
}, | ||
], | ||
}) | ||
} | ||
} | ||
}) | ||
|
||
async function syncViewer(menuTree, eventType, viewerId) { | ||
const viewerLayers = new Set() | ||
|
||
const findChilrenInMenu = (menu, viewerId) => { | ||
const { children } = menu | ||
|
||
if (children) { | ||
children.forEach((child) => { | ||
if (child.id === viewerId && child.children) { | ||
child.children.forEach((viewerLayer) => { | ||
viewerLayers.add(viewerLayer.id) | ||
}) | ||
} | ||
|
||
findChilrenInMenu(child, viewerId) | ||
}) | ||
} | ||
} | ||
|
||
menuTree.forEach((viewer) => { | ||
findChilrenInMenu(viewer, viewerId) | ||
}) | ||
|
||
const viewerLayersArray = Array.from(viewerLayers) | ||
|
||
|
||
const requestsPromises = viewerLayersArray.map( | ||
async (viewerLayerId) => { | ||
await syncViewerLayers(menuTree, eventType, viewerLayerId) | ||
} | ||
) | ||
|
||
const results = await Promise.allSettled(requestsPromises) | ||
} | ||
|
||
async function syncViewerLayers(menuTree, eventType, viewerLayerId) { | ||
const geonetworkInstances = findGeonetworkInstances(menuTree, viewerLayerId) | ||
|
||
const geonetworkInstancesArray = Array.from(geonetworkInstances) | ||
|
||
const xml = await fetchViewerLayerXML({ id: viewerLayerId }) | ||
|
||
// Can occur when no update needs to be done (because there is no factsheet or inspireMetadata) | ||
if (xml === null) { | ||
return | ||
} | ||
|
||
const requestsPromises = geonetworkInstancesArray.map( | ||
async ([_, geonetworkInstance]) => { | ||
const { baseUrl, username, password } = geonetworkInstance | ||
|
||
const geonetwork = new Geonetwork( | ||
baseUrl + 'geonetwork/srv/api', | ||
username, | ||
password | ||
) | ||
|
||
switch (eventType) { | ||
case 'create': { | ||
await geonetwork.recordsRequest({ | ||
url: '?publishToAll=true', | ||
method: 'PUT', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: xml, | ||
}) | ||
|
||
break | ||
} | ||
|
||
case 'publish': { | ||
await geonetwork.recordsRequest({ | ||
url: '?uuidProcessing=OVERWRITE&publishToAll=true', | ||
method: 'PUT', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: xml, | ||
}) | ||
|
||
break | ||
} | ||
} | ||
|
||
switch (eventType) { | ||
case 'create': | ||
case 'publish': | ||
const { viewerLayer } = await datocmsRequest({ | ||
query: viewerLayerByIdQuery, | ||
variables: { id: viewerLayerId }, | ||
}) | ||
|
||
await addThumbnailsToRecord(viewerLayer?.layer?.thumbnails, viewerLayerId, geonetwork) | ||
} | ||
} | ||
) | ||
|
||
const results = await Promise.allSettled(requestsPromises) | ||
|
||
const errors = results.filter(result => result.status === 'rejected') | ||
|
||
if (errors.length) { | ||
const errorMessage = `<ul>${errors.map(error => `<li>${error.reason}</li>`).join('')}</ul>` | ||
throw new Error(errorMessage) | ||
} | ||
|
||
} | ||
|
||
function findEmailContactsForLayerId(menuTree, layerId) { | ||
const contacts = new Set() | ||
|
||
menuTree.forEach((viewer) => { | ||
|
||
const findInMenu = (menu) => { | ||
const { children } = menu | ||
|
||
if (children) { | ||
children.forEach((child) => { | ||
|
||
if (child.layer.id === layerId) { | ||
const { errorNotificationContacts } = viewer | ||
|
||
if (errorNotificationContacts.length) { | ||
for (let { email } of errorNotificationContacts) { | ||
contacts.add(email) | ||
} | ||
} | ||
} | ||
|
||
findInMenu(child) | ||
}) | ||
} | ||
} | ||
|
||
findInMenu(viewer) | ||
}) | ||
|
||
return contacts | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import convert from 'xml-js' | ||
import { withServerDefaults } from '../lib/with-server-defaults' | ||
import { contentTypes } from '../lib/constants' | ||
import { fetchViewerLayerXML } from '../lib/fetch-viewer-layer-xml' | ||
|
||
export const handler = withServerDefaults(async (event, _) => { | ||
const { id, format } = event.queryStringParameters | ||
|
||
if (!id) { | ||
return { | ||
statusCode: 404, | ||
body: JSON.stringify({ error: 'id query parameter is required' }), | ||
} | ||
} | ||
|
||
if (!format) { | ||
return { | ||
statusCode: 404, | ||
body: JSON.stringify({ error: 'format query parameter is required' }), | ||
} | ||
} | ||
|
||
if (!['json', 'xml'].includes(format)) { | ||
return { | ||
statusCode: 400, | ||
body: JSON.stringify({ | ||
error: | ||
'format query parameter must be one of the following values: json|xml', | ||
}), | ||
} | ||
} | ||
|
||
let formatted = await fetchViewerLayerXML({ | ||
id, | ||
}) | ||
|
||
if (format === 'json') { | ||
formatted = convert.xml2json(formatted, { | ||
compact: true, | ||
}) | ||
} | ||
|
||
return { | ||
body: formatted, | ||
headers: { | ||
'content-type': contentTypes[format], | ||
}, | ||
} | ||
}) |
This file contains 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
This file contains 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
Oops, something went wrong.