From 37419b8a2a4296069e9b8e516bb0148f627e9bbd Mon Sep 17 00:00:00 2001 From: david Date: Mon, 11 Nov 2019 17:43:08 +0100 Subject: [PATCH] . --- helpers/message.helper.js | 11 + helpers/messages.json | 16 ++ helpers/notification.helpers.js | 48 ++++ helpers/push.helper.js | 133 +++++++++++ modules/events/event.controller.js | 65 ++++-- .../notification/notification.controller.js | 124 +--------- modules/notification/notification.service.js | 214 +++++++++--------- .../notification_detail.service.js | 1 + package.json | 1 + 9 files changed, 374 insertions(+), 239 deletions(-) create mode 100644 helpers/notification.helpers.js create mode 100644 helpers/push.helper.js diff --git a/helpers/message.helper.js b/helpers/message.helper.js index d8a718a..4fb0a84 100644 --- a/helpers/message.helper.js +++ b/helpers/message.helper.js @@ -1,5 +1,7 @@ 'use strict'; +const tinytim = require('tinytim').tim; + //////////////////////////////////////////////////////////////////////////////// // CONSTANTS //////////////////////////////////////////////////////////////////////////////// @@ -29,7 +31,16 @@ function buildMessage(text) { return buildGenericMessage(TITLE_MESSAGE, text) } +function tinytom(literal, data) { + if (typeof literal === 'object' && literal !== null) { + return JSON.parse(tinytim(JSON.stringify(literal), data)); + } else { + return tinytim(literal, data); + } +} + module.exports = { + tinytom, buildErrorMessage, buildMessage, //For testing diff --git a/helpers/messages.json b/helpers/messages.json index 7f7b020..a95822e 100644 --- a/helpers/messages.json +++ b/helpers/messages.json @@ -21,5 +21,21 @@ "html": "¡Hola!,

Solicitaste cambiar la contraseña, hemos creado una nueva.

Entra en la app con tu dirección de email y esta clave: {{password}}

Fundación Lo Que De Verdad Importa." } } + }, + "push": { + "confirmInvitation": { + "title": "{{ congress }}", + "message": "Tu inscripción al congreso de {{ congress }} está confirmada.", + "data": { + "type": "message", + "title": "{{ congress }}", + "message": "Tu inscripción al congreso de {{ congress }} está confirmada.\nPor favor, si no puedes asistir al congreso, elimina tu inscripción para que otra persona pueda asistir.\n", + "button": { + "caption": "Ver mi entrada", + "screen": "Ticket", + "paramId": "{{ ticketId }}" + } + } + } } } \ No newline at end of file diff --git a/helpers/notification.helpers.js b/helpers/notification.helpers.js new file mode 100644 index 0000000..663c763 --- /dev/null +++ b/helpers/notification.helpers.js @@ -0,0 +1,48 @@ +const moment = require('moment'); +const { tinytom } = require('./message.helper'); +const messages = require('./messages.json'); + + +const createNotification = (data) => { + return { + date: data.date, + title: data.title, + body: data.message, + ttl: data.ttl, + priority: data.priority ? data.priority : 'high', + recipients: data.recipients, + data: data.data, + userId: data.userId, + _displayInForeground: true, + sound: 'default', + } +} + +createNotificationValidatedInscription = (inscription) => { + let jsonMessage = messages.push.confirmInvitation; + + const jsonNotification = tinytom(jsonMessage, { + congress: inscription.event.name, + ticketId: inscription.id, + }); + + return { + ...jsonNotification, + date: moment(), + priority: "high", + _displayInForeground: true, + sound: 'default', + recipients: { + "userIds": [ + inscription.user.id, + ] + }, + } +} + + +module.exports = { + createNotificationValidatedInscription, + createNotification, + //createMessageNotification +} diff --git a/helpers/push.helper.js b/helpers/push.helper.js new file mode 100644 index 0000000..6a4fb8b --- /dev/null +++ b/helpers/push.helper.js @@ -0,0 +1,133 @@ +const { Expo } = require('expo-server-sdk'); + +// Create a new Expo SDK client +const expo = new Expo(); + +const createPushMessage = (data) => { + console.log('---------------------------------'); + console.log(data); + console.log('---------------------------------'); + return { + title: data.title, + body: data.body, + ttl: data.ttl, + priority: data.priority, + userId: data.userId, + to: data.to, + notificationId: data.id, + data: data.data, + _displayInForeground: true, + sound: 'default', + } +}; + + + + + +const sendPushMessage = async (messages) => { + + // The Expo push notification service accepts batches of notifications so + // that you don't need to send 1000 requests to send 1000 notifications. We + // recommend you batch your notifications to reduce the number of requests + // and to compress them (notifications with similar content will get + // compressed). + + /** + * There is a limit on the number of push notifications (100) you can send at once.Use + * `chunkPushNotifications` to divide an array of push notification messages into appropriately + * sized chunks + */ + + // Later, after the Expo push notification service has delivered the + // notifications to Apple or Google (usually quickly, but allow the the service + // up to 30 minutes when under load), a "receipt" for each notification is + // created. The receipts will be available for at least a day; stale receipts + // are deleted. + // + // The ID of each receipt is sent back in the response "ticket" for each + // notification. In summary, sending a notification produces a ticket, which + // contains a receipt ID you later use to get the receipt. + // + // The receipts may contain error codes to which you must respond. In + // particular, Apple or Google may block apps that continue to send + // notifications to devices that have blocked notifications or have uninstalled + // your app. Expo does not control this policy and sends back the feedback from + // Apple and Google so you can handle it appropriately. + + let chunks = expo.chunkPushNotifications(messages); + return { + messages, + tickets: await _sendPushNotificationsAsync(chunks) + }; +}; + +module.exports = { + sendPushMessage, + createPushMessage, +} + + +const _sendPushNotificationsAsync = async function (chunks) { + return new Promise(async function (resolve) { + let tickets = []; + // Send the chunks to the Expo push notification service. There are + // different strategies you could use. A simple one is to send one chunk at a + // time, which nicely spreads the load out over time: + for (let chunk of chunks) { + try { + let ticketChunk = await expo.sendPushNotificationsAsync(chunk); + tickets.push(...ticketChunk); + + // NOTE: If a ticket contains an error code in ticket.details.error, you + // must handle it appropriately. The error codes are listed in the Expo + // documentation: + // https://docs.expo.io/versions/latest/guides/push-notifications#response-format + + } catch (error) { + console.error(error); + } + } + + resolve(tickets); + }); +}; + +const _getPushNotificationsResultAsync = async function (receiptIdChunks) { + return new Promise(async function (resolve) { + // Like sending notifications, there are different strategies you could use + // to retrieve batches of receipts from the Expo service. + + let result = []; + + console.log(receiptIdChunks); + + for (let chunk of receiptIdChunks) { + try { + let receipts = await expo.getPushNotificationReceiptsAsync(chunk); + console.log('hola', receipts); + + // The receipts specify whether Apple or Google successfully received the + // notification and information about an error, if one occurred. + for (let key in receipts) { + if (receipts[key].status === 'ok') { + result.push[receipts[key]]; + continue; + } else if (receipts[key].status === 'error') { + console.error(`There was an error sending a notification: ${receipts[key].message}`); + if (receipts[key].details && receipts[key].details.error) { + // The error codes are listed in the Expo documentation: + // https://docs.expo.io/versions/latest/guides/push-notifications#response-format + // You must handle the errors appropriately. + console.error(`The error code is ${receipts[key].details.error}`); + } + } + } + } catch (error) { + console.error(error); + } + } + + resolve(result); + }); +} diff --git a/modules/events/event.controller.js b/modules/events/event.controller.js index cc11e0e..fc6a6f2 100644 --- a/modules/events/event.controller.js +++ b/modules/events/event.controller.js @@ -4,11 +4,14 @@ const httpStatus = require('http-status'); const generateControllers = require('../../core/controllers'); const QRHelper = require('../../helpers/qr.helper'); const emailHelper = require('../../helpers/mail.helper'); +const notificationHelper = require('../../helpers/notification.helpers'); const path = require("path"); const messages = require('../../helpers/messages.json'); const eventService = require('./event.service'); const eventReservationService = require('./events_reservations.service'); const eventInscriptionService = require('./events_inscriptions.service'); +const notificationController = require('../notification/notification.controller'); + const { extractParamsFromRequest, handleErrorResponse, handleResultResponse } = require('../../helpers/controller.helper'); //PRUEBA @@ -26,25 +29,25 @@ function generateMemberInscription (user, inscription, reservation) { let memberInscription = null; if (user && inscription) { memberInscription = { - marketing_memberId: null, - email: user.email, - name: user.name, - surname: user.surname, - source: inscription.source, - event_name: (inscription.event) ? inscription.event.name : 'N/A', - event_date: (inscription.event) ? inscription.event.init_date : 'N/A', - reservation_code: (reservation) ? reservation.reservation_code : null, - date_inscription: inscription.date, - code_ticket: inscription.code_ticket, - validated: inscription.validated, - color: (reservation) ? reservation.color : null, - description: ((reservation) ? reservation.description : 'Entrada').toUpperCase(), - entity: (reservation) ? reservation.Entity.name : user.entityId, - userId: user.id, - qrConfig: null, - qrCode: null, + marketing_memberId: null, + email: user.email, + name: user.name, + surname: user.surname, + source: inscription.source, + event_name: (inscription.event) ? inscription.event.name : 'N/A', + event_date: (inscription.event) ? inscription.event.init_date : 'N/A', + reservation_code: (reservation) ? reservation.reservation_code : null, + date_inscription: inscription.date, + code_ticket: inscription.code_ticket, + validated: inscription.validated, + color: (reservation) ? reservation.color : null, + description: ((reservation) ? reservation.description : 'Entrada').toUpperCase(), + entity: (reservation) ? reservation.Entity.name : user.entityId, + userId: user.id, + qrConfig: null, + qrCode: null, - } + } } return memberInscription; @@ -264,19 +267,31 @@ console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ', member); - //Mandar correo de confirmacion de desinscripcion + + //Mandar correo de confirmacion de inscripcion try { + + var member = generateMemberInscription(inscription.user, inscription, inscription.reservation); + member.marketing_memberId = await eventInscriptionService._addMember(marketingListIdEvent, member); + eventInscriptionService._updateMarketingMemberOfInscription(inscription.id, member.marketing_memberId); + member.qrConfig = generateQRConfig(member); + member.qrCode = await QRHelper.getInscriptionQRCode(member.qrConfig); + console.log('mandar correo>>>>>>>>>>>>>>>>>>>>> ', member); emailHelper.sendTicket(generateHeaderMail(member), generateBodyMail(member)) } catch (error) { console.log('No se ha podido mandar email con entrada'); }; + + + try { + let notification = notificationHelper.createNotificationValidatedInscription(inscription); + notificationController.sendNotification(notification); + + console.log(result); + } catch (error) { + console.log('No se ha podido mandar email con entrada'); + }; }; return handleResultResponse("Inscripción validada", null, params, res, httpStatus.OK); diff --git a/modules/notification/notification.controller.js b/modules/notification/notification.controller.js index e8110e2..500d329 100644 --- a/modules/notification/notification.controller.js +++ b/modules/notification/notification.controller.js @@ -8,6 +8,8 @@ const notificationDetailService = require('./notification_detail.service'); const userDeviceService = require('./user_device.service'); const eventInscriptionService = require('../events/events_inscriptions.service'); const { usersIdsComposer } = require('../../helpers/composes.helper'); +const pushHelper = require('../../helpers/push.helper'); +const notificationHelper = require('../../helpers/notification.helpers'); const { extractParamsFromRequest, handleErrorResponse, handleResultResponse } = require('../../helpers/controller.helper'); @@ -76,85 +78,17 @@ const extraControllers = { } - try { - let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId }}, context).then(function(result) { - return new Promise(function(resolve) { resolve(result.rows) }); + try { + let notification = notificationHelper.createNotification ({ + ...body, + userId: context.user.id }); - let saveNotificationPromise = (notification) => notificationService.createNotification(notification); - let sendNotificationsPromise = (messages) => notificationService.sendNotification(messages); - let disableUserDevicePromise = (token) => userDeviceService.update({ params: { - token: token, - }}, { valid: 0, invalidated: moment() }, context); - - let disableInvalidUserDevicesPromise = (userDevices) => { - return new Promise(function (resolve) { - let _userDevices = []; - userDevices.forEach(async function (userDevice) { - console.log(userDevice); - if (!userDeviceService.isValidPushToken(userDevice.token)) { - await disableUserDevicePromise(userDevice.token); - } else { - _userDevices.push(userDevice); - } - }); - - resolve(_userDevices) - }); - }; - - let disableUserDevicesWithErrorStatus = (messages, tickets) => { - return new Promise(function (resolve) { - tickets.forEach(async function (ticket, index) { - if ((ticket.status === 'error') && (ticket.details.error === 'DeviceNotRegistered')) { - await disableUserDevicePromise(messages[index].to) - } - }); - resolve(true); - }); - } - - let saveResponseStatusPromise = (messages, tickets) => notificationDetailService.saveNotificationDetails(messages, tickets); - - let notificationRecord = { - date: body.date, - title: body.title, - body: body.message, - ttl: body.ttl, - priority: body.priority, - recipients: body.recipients, - data: body.data, - userId: context.user.id, - }; console.log('--------------------'); - console.log(notificationRecord); + console.log(notification); - let buildMessagesPromise = (userDevices) => { - return new Promise(function (resolve) { - let messages = []; - - userDevices.forEach(async function (userDevice) { - messages.push({ - title: notificationRecord.title, - body: notificationRecord.body, - ttl: notificationRecord.ttl, - priority: notificationRecord.priority, - userId: userDevice.userId, - to: userDevice.token, - notificationId: notificationRecord.id, - data: notificationRecord.data, - _displayInForeground: true, - sound: 'default', - }); - }); - - resolve(messages) - }); - }; - - let getUserIds = async () => { if (userIds) { - return new Promise(function(resolve) { resolve(userIds) } ); + return new Promise(function(resolve) { resolve(userIds) } ); } else if (eventId && segment) { switch (segment) { //Todos los inscritos tanto invitados como libres @@ -182,46 +116,10 @@ const extraControllers = { } else { return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing event and segment'), res) } - } - - let getUserDevicesList = []; + } + + receipt = notificationService.sendNotification(notification, await getUserIds()); - Promise.all([ - saveNotificationPromise(notificationRecord), - getUserIds() - ]) - .then(function(result) { - notificationRecord = result[0]; - userIds = result[1]; - - userIds.forEach(function (userId) { - console.log(userId); - getUserDevicesList.push(getUserDevicesPromise(userId)); - }); - - return Promise.all(getUserDevicesList) - }).then(function (userDeviceList) { - console.log(userDeviceList); - let result = []; - userDeviceList.forEach(function (elements) { - elements.forEach(function (item) { - result.push(item) - }) - }); - return new Promise(function (resolve) { resolve(result); }); - }) - .then(disableInvalidUserDevicesPromise) - .then(buildMessagesPromise) - .then(sendNotificationsPromise) - .then(function({ messages, tickets }) { - let _saveStatus = saveResponseStatusPromise(messages, tickets); - let _disableDevices = disableUserDevicesWithErrorStatus(messages, tickets); - - return Promise.all([_saveStatus, _disableDevices]); - }) - .then(function (details) { - console.log(details); - }); } catch (error) { console.error(error); return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', error, res) diff --git a/modules/notification/notification.service.js b/modules/notification/notification.service.js index 4b44c80..872f25c 100644 --- a/modules/notification/notification.service.js +++ b/modules/notification/notification.service.js @@ -1,14 +1,27 @@ const moment = require('moment'); -const { Expo } = require('expo-server-sdk'); -const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper'); -const models = require('../../core/models'); -// Create a new Expo SDK client -const expo = new Expo(); +const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper'); +const userDeviceService = require('./user_device.service'); +const notificationDetailService = require('./notification_detail.service'); +const pushHelper = require('../../helpers/push.helper'); +const models = require('../../core/models'); const extraMethods = { - createNotification: ({ date, title, body, ttl, priority, recipients, data, userId }) => { + createNotification: (data) => { + return { + date: data.date, + title: data.title, + body: data.message, + ttl: data.ttl, + priority: data.priority, + recipients: data.recipients, + data: data.data, + userId: data.userId, + } + }, + + saveNotification: ({ date, title, body, ttl, priority, recipients, data, userId }) => { console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>< { + sendNotification: (notification, userIds) => { - // The Expo push notification service accepts batches of notifications so - // that you don't need to send 1000 requests to send 1000 notifications. We - // recommend you batch your notifications to reduce the number of requests - // and to compress them (notifications with similar content will get - // compressed). + console.log('sendNofitication -----------------------------------------------'); + console.log(notification, userIds); - /** - * There is a limit on the number of push notifications (100) you can send at once.Use - * `chunkPushNotifications` to divide an array of push notification messages into appropriately - * sized chunks - */ + let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId } }, {}).then(function (result) { + return new Promise(function (resolve) { resolve(result.rows) }); + }); + let saveNotificationPromise = (notification) => extraMethods.saveNotification(notification); + let sendNotificationsPromise = (messages) => pushHelper.sendPushMessage(messages); + let disableUserDevicePromise = (token) => userDeviceService.update({ + params: { + token: token, + } + }, { valid: 0, invalidated: moment() }, {}); - // Later, after the Expo push notification service has delivered the - // notifications to Apple or Google (usually quickly, but allow the the service - // up to 30 minutes when under load), a "receipt" for each notification is - // created. The receipts will be available for at least a day; stale receipts - // are deleted. - // - // The ID of each receipt is sent back in the response "ticket" for each - // notification. In summary, sending a notification produces a ticket, which - // contains a receipt ID you later use to get the receipt. - // - // The receipts may contain error codes to which you must respond. In - // particular, Apple or Google may block apps that continue to send - // notifications to devices that have blocked notifications or have uninstalled - // your app. Expo does not control this policy and sends back the feedback from - // Apple and Google so you can handle it appropriately. + let disableInvalidUserDevicesPromise = (userDevices) => { + return new Promise(function (resolve) { + let _userDevices = []; + userDevices.forEach(async function (userDevice) { + console.log(userDevice); + if (!userDeviceService.isValidPushToken(userDevice.token)) { + await disableUserDevicePromise(userDevice.token); + } else { + _userDevices.push(userDevice); + } + }); - let chunks = expo.chunkPushNotifications(messages); - return { - messages, - tickets: await _sendPushNotificationsAsync(chunks) + resolve(_userDevices) + }); }; + + let disableUserDevicesWithErrorStatus = (messages, tickets) => { + return new Promise(function (resolve) { + tickets.forEach(async function (ticket, index) { + if ((ticket.status === 'error') && (ticket.details.error === 'DeviceNotRegistered')) { + await disableUserDevicePromise(messages[index].to) + } + }); + resolve(true); + }); + } + + let saveResponseStatusPromise = (messages, tickets) => notificationDetailService.saveNotificationDetails(messages, tickets); + + + let buildMessagesPromise = (userDevices) => { + return new Promise(function (resolve) { + let messages = []; + + userDevices.forEach(async function (userDevice) { + messages.push(pushHelper.createPushMessage({ + ...notification, + userId: userDevice.userId, + to: userDevice.token, + })); + }); + resolve(messages) + }); + }; + + + let getUserDevicesList = []; + + saveNotificationPromise(notification) + .then(function (notificationRecord) { + notification = notificationRecord.toJSON(); + userIds.forEach(function (userId) { + console.log(userId); + getUserDevicesList.push(getUserDevicesPromise(userId)); + }); + + return Promise.all(getUserDevicesList) + }).then(function (userDeviceList) { + console.log(userDeviceList); + let result = []; + userDeviceList.forEach(function (elements) { + elements.forEach(function (item) { + result.push(item) + }) + }); + return new Promise(function (resolve) { resolve(result); }); + }) + .then(disableInvalidUserDevicesPromise) + .then(buildMessagesPromise) + .then(sendNotificationsPromise) + .then(function ({ messages, tickets }) { + let _saveStatus = saveResponseStatusPromise(messages, tickets); + let _disableDevices = disableUserDevicesWithErrorStatus(messages, tickets); + + return Promise.all([_saveStatus, _disableDevices]); + }) + .then(function (details) { + console.log(details); + return details; + }); }, + + getNotificationsWithoutReceipt: async() => { }, @@ -78,67 +153,4 @@ const extraMethods = { module.exports = generateService(models.Notification, extraMethods); -const _sendPushNotificationsAsync = async function (chunks) { - return new Promise(async function (resolve) { - let tickets = []; - // Send the chunks to the Expo push notification service. There are - // different strategies you could use. A simple one is to send one chunk at a - // time, which nicely spreads the load out over time: - for (let chunk of chunks) { - try { - let ticketChunk = await expo.sendPushNotificationsAsync(chunk); - tickets.push(...ticketChunk); - - // NOTE: If a ticket contains an error code in ticket.details.error, you - // must handle it appropriately. The error codes are listed in the Expo - // documentation: - // https://docs.expo.io/versions/latest/guides/push-notifications#response-format - - } catch (error) { - console.error(error); - } - } - - resolve(tickets); - }); -}; - -const _getPushNotificationsResultAsync = async function (receiptIdChunks) { - return new Promise(async function (resolve) { - // Like sending notifications, there are different strategies you could use - // to retrieve batches of receipts from the Expo service. - - let result = []; - - console.log(receiptIdChunks); - - for (let chunk of receiptIdChunks) { - try { - let receipts = await expo.getPushNotificationReceiptsAsync(chunk); - console.log('hola', receipts); - - // The receipts specify whether Apple or Google successfully received the - // notification and information about an error, if one occurred. - for (let key in receipts) { - if (receipts[key].status === 'ok') { - result.push[receipts[key]]; - continue; - } else if (receipts[key].status === 'error') { - console.error(`There was an error sending a notification: ${receipts[key].message}`); - if (receipts[key].details && receipts[key].details.error) { - // The error codes are listed in the Expo documentation: - // https://docs.expo.io/versions/latest/guides/push-notifications#response-format - // You must handle the errors appropriately. - console.error(`The error code is ${receipts[key].details.error}`); - } - } - } - } catch (error) { - console.error(error); - } - } - - resolve(result); - }); -} diff --git a/modules/notification/notification_detail.service.js b/modules/notification/notification_detail.service.js index 3606f99..e53d4bb 100644 --- a/modules/notification/notification_detail.service.js +++ b/modules/notification/notification_detail.service.js @@ -7,6 +7,7 @@ const extraMethods = { saveNotificationDetails: async function (messages, tickets) { return new Promise(function (resolve) { messages.forEach(async function (message, index) { + console.log(message); let notification = models.NotificationDetail.build({ ...message, token: message.to, diff --git a/package.json b/package.json index 07a4980..20b61fe 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "sanitize-filename": "^1.6.2", "sequelize": "^5.16.0", "sharp": "^0.23.0", + "tinytim": "^0.1.1", "unique-file-name": "^1.0.0", "vimeo": "^2.1.1", "vm": "^0.1.0",