|
|
@@ -0,0 +1,294 @@
|
|
|
+'use strict';
|
|
|
+
|
|
|
+const logger = require('../logger');
|
|
|
+const nantum = require('../modules/nantum');
|
|
|
+const { vtnId } = require('../config');
|
|
|
+
|
|
|
+function calculateEventStatus(notificationTime, startTime, endTime) {
|
|
|
+ const nowMillis = new Date().getTime();
|
|
|
+ if (nowMillis < new Date(startTime).getTime()) {
|
|
|
+ return 'far';
|
|
|
+ }
|
|
|
+ if (nowMillis > new Date(endTime).getTime()) {
|
|
|
+ return 'completed';
|
|
|
+ }
|
|
|
+
|
|
|
+ return 'active';
|
|
|
+}
|
|
|
+
|
|
|
+function calculateDurationSeconds(startTime, endTime) {
|
|
|
+ return Math.round(
|
|
|
+ (new Date(endTime).getTime() - new Date(startTime).getTime()) / 1000,
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function calculateDuration(startTime, endTime) {
|
|
|
+ return `PT${calculateDurationSeconds(startTime, endTime)}S`;
|
|
|
+}
|
|
|
+
|
|
|
+function calculateNotificationDuration(notificationTime, startTime) {
|
|
|
+ if (!notificationTime) {
|
|
|
+ return 'PT0S';
|
|
|
+ }
|
|
|
+ return calculateDuration(notificationTime, startTime);
|
|
|
+}
|
|
|
+
|
|
|
+function calculateEventIntervals(eventInfoValues, eventDurationSeconds) {
|
|
|
+ //TODO: this is likely incorrect. Get more details on the event_info_value data model.
|
|
|
+
|
|
|
+ let result = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < eventInfoValues.length; i++) {
|
|
|
+ const eventInfoValue = eventInfoValues[i];
|
|
|
+ const nextOffset =
|
|
|
+ i === eventInfoValues.length - 1
|
|
|
+ ? eventDurationSeconds - eventInfoValue.timeOffset
|
|
|
+ : eventInfoValues[i + 1].timeOffset;
|
|
|
+ result.push({
|
|
|
+ signalPayloads: [eventInfoValue.value],
|
|
|
+ duration: `PT${nextOffset}S`,
|
|
|
+ uid: `${i + 1}`,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+function calculateEventSignals(eventInstances, eventDurationSeconds) {
|
|
|
+ return eventInstances.map(eventInstance => {
|
|
|
+ return {
|
|
|
+ signalName: eventInstance.event_type_id,
|
|
|
+ signalId: '112233445566',
|
|
|
+ signalType: 'level',
|
|
|
+ intervals: calculateEventIntervals(
|
|
|
+ eventInstance.event_info_values,
|
|
|
+ eventDurationSeconds,
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function convertToOadrEvents(nantumEvent) {
|
|
|
+ if (!nantumEvent.dr_event_data) {
|
|
|
+ // no event
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ const nowMillis = new Date().getTime();
|
|
|
+ if (
|
|
|
+ nowMillis < new Date(nantumEvent.dr_event_data.notification_time).getTime()
|
|
|
+ ) {
|
|
|
+ return []; // not in the notification period yet
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ eventDescriptor: {
|
|
|
+ eventId: nantumEvent.event_identifier,
|
|
|
+ modificationNumber: nantumEvent.event_mod_number,
|
|
|
+ marketContext: 'http://MarketContext1',
|
|
|
+ createdDateTime: '2020-04-14T16:06:39.000Z',
|
|
|
+ eventStatus: calculateEventStatus(
|
|
|
+ nantumEvent.dr_event_data.notification_time,
|
|
|
+ nantumEvent.dr_event_data.start_time,
|
|
|
+ nantumEvent.dr_event_data.end_time,
|
|
|
+ ),
|
|
|
+ testEvent: nantumEvent.test_event,
|
|
|
+ priority: 0,
|
|
|
+ },
|
|
|
+ activePeriod: {
|
|
|
+ startDate: nantumEvent.dr_event_data.start_time,
|
|
|
+ duration: calculateDuration(
|
|
|
+ nantumEvent.dr_event_data.start_time,
|
|
|
+ nantumEvent.dr_event_data.end_time,
|
|
|
+ ),
|
|
|
+ notificationDuration: calculateNotificationDuration(
|
|
|
+ nantumEvent.dr_event_data.notification_time,
|
|
|
+ nantumEvent.dr_event_data.start_time,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ signals: {
|
|
|
+ event: calculateEventSignals(
|
|
|
+ nantumEvent.dr_event_data.event_instance,
|
|
|
+ calculateDurationSeconds(
|
|
|
+ nantumEvent.dr_event_data.start_time,
|
|
|
+ nantumEvent.dr_event_data.end_time,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ target: {
|
|
|
+ venId: [nantumEvent.client_id],
|
|
|
+ },
|
|
|
+ responseRequired: 'always',
|
|
|
+ },
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+async function retrieveEvents(
|
|
|
+ obj,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+) {
|
|
|
+ logger.info(
|
|
|
+ 'retrieveEvents',
|
|
|
+ obj,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+ );
|
|
|
+
|
|
|
+ const requestVenId = obj.venId;
|
|
|
+
|
|
|
+ if (!requestVenId) {
|
|
|
+ const error = new Error('No VenID in request');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (requestVenId !== clientCertificateFingerprint) {
|
|
|
+ // as per certification item #512, venId MUST be case-sensitive
|
|
|
+ const error = new Error('VenID does not match certificate');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!clientCertificateCn) {
|
|
|
+ const error = new Error('Could not determine CN from client certificate');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ const event = await nantum.fetchEvent(requestVenId);
|
|
|
+
|
|
|
+ return {
|
|
|
+ responseCode: '200',
|
|
|
+ responseDescription: 'OK',
|
|
|
+ responseRequestId: obj.requestId || '',
|
|
|
+ requestId: obj.requestId || '',
|
|
|
+ vtnId: vtnId,
|
|
|
+ events: convertToOadrEvents(event),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+async function updateOptType(
|
|
|
+ oadrCreatedEvent,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+) {
|
|
|
+ logger.info(
|
|
|
+ 'updateOptType',
|
|
|
+ oadrCreatedEvent,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+ );
|
|
|
+
|
|
|
+ const requestVenId = oadrCreatedEvent.venId;
|
|
|
+
|
|
|
+ if (!requestVenId) {
|
|
|
+ const error = new Error('No VenID in request');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (requestVenId !== clientCertificateFingerprint) {
|
|
|
+ // as per certification item #512, venId MUST be case-sensitive
|
|
|
+ const error = new Error('VenID does not match certificate');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ const nantumRegistration = await nantum.fetchRegistration(requestVenId);
|
|
|
+
|
|
|
+ const opted = nantumRegistration.opted || [];
|
|
|
+
|
|
|
+ //TODO: more validation: VEN may opt into an event that doesn't exist. VEN may opt into an old version of an event
|
|
|
+ // (modificationNumber doesn't match). May opt into a completed event. Indicate error(s) to client.
|
|
|
+
|
|
|
+ for (const eventResponse of oadrCreatedEvent.eventResponses) {
|
|
|
+ // remove existing opts for this eventId
|
|
|
+ nantumRegistration.opted = [
|
|
|
+ ...opted.filter(optedItem => optedItem.eventId !== eventResponse.eventId),
|
|
|
+ ];
|
|
|
+ nantumRegistration.opted.push({
|
|
|
+ eventId: eventResponse.eventId,
|
|
|
+ modificationNumber: eventResponse.modificationNumber,
|
|
|
+ optType: eventResponse.optType,
|
|
|
+ });
|
|
|
+ await nantum.updateRegistration(nantumRegistration);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ responseCode: '200',
|
|
|
+ responseDescription: 'OK',
|
|
|
+ venId: clientCertificateFingerprint,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+async function filterOutAcknowledgedEvents(venId, events) {
|
|
|
+ const nantumRegistration = await nantum.fetchRegistration(venId);
|
|
|
+ const opted = nantumRegistration.opted || [];
|
|
|
+ return events.filter(
|
|
|
+ event =>
|
|
|
+ opted.filter(
|
|
|
+ optedItem =>
|
|
|
+ optedItem.eventId === event.eventDescriptor.eventId &&
|
|
|
+ optedItem.modificationNumber ===
|
|
|
+ event.eventDescriptor.modificationNumber,
|
|
|
+ ).length === 0,
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+async function pollForEvents(
|
|
|
+ obj,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+) {
|
|
|
+ logger.info(
|
|
|
+ 'pollForEvents',
|
|
|
+ obj,
|
|
|
+ clientCertificateCn,
|
|
|
+ clientCertificateFingerprint,
|
|
|
+ );
|
|
|
+
|
|
|
+ const requestVenId = obj.venId;
|
|
|
+
|
|
|
+ if (!requestVenId) {
|
|
|
+ const error = new Error('No VenID in request');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (requestVenId !== clientCertificateFingerprint) {
|
|
|
+ // as per certification item #512, venId MUST be case-sensitive
|
|
|
+ const error = new Error('VenID does not match certificate');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!clientCertificateCn) {
|
|
|
+ const error = new Error('Could not determine CN from client certificate');
|
|
|
+ error.responseCode = 452;
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ const event = await nantum.fetchEvent(requestVenId);
|
|
|
+ const filteredEvents = await filterOutAcknowledgedEvents(
|
|
|
+ requestVenId,
|
|
|
+ convertToOadrEvents(event),
|
|
|
+ );
|
|
|
+
|
|
|
+ if (filteredEvents.length > 0) {
|
|
|
+ return {
|
|
|
+ responseCode: '200',
|
|
|
+ responseDescription: 'OK',
|
|
|
+ responseRequestId: '', // required field, but empty is allowed as per spec
|
|
|
+ requestId: '',
|
|
|
+ vtnId: vtnId,
|
|
|
+ events: filteredEvents,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ pollForEvents,
|
|
|
+ retrieveEvents,
|
|
|
+ updateOptType,
|
|
|
+};
|