Merge branch 'master' of wopr.rodax-software.com:lqdvi/app2-api

This commit is contained in:
David Arranz 2019-11-14 18:20:31 +01:00
commit 575044f8a0
15 changed files with 476 additions and 341 deletions

View File

@ -116,12 +116,9 @@ passport.use('jwt', new CustomStrategy(async (req, done) => {
let user = await authService.extraMethods.findUser({ id: result.id }); let user = await authService.extraMethods.findUser({ id: result.id });
if (user) { if (user) {
user = user.toJSON(); user = user.toJSON();
if (appVersion) { const result = userService._updateLastLoginAndVersionUser(user.id, appVersion);
if (user.app_version != appVersion){ user.app_version = appVersion;
const result = userService._updateLastLoginAndVersionUser(user.id, appVersion);
user.app_version = appVersion;
}
}
delete user.password; delete user.password;
console.log('Logged in Successfully'); console.log('Logged in Successfully');
return done(null, user, { message: 'Logged in Successfully' }); return done(null, user, { message: 'Logged in Successfully' });

View File

@ -258,9 +258,7 @@ const usersIdsComposer = (inscriptions) => {
let usersId = [] let usersId = []
if (inscriptions) { if (inscriptions) {
inscriptions.map((inscription) => { inscriptions.map((inscription) => {
usersId.push({ usersId.push(inscription.userId);
userId: inscription.userId,
});
}); });
}; };
return usersId; return usersId;

View File

@ -1,5 +1,7 @@
'use strict'; 'use strict';
const tinytim = require('tinytim').tim;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// CONSTANTS // CONSTANTS
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -29,7 +31,16 @@ function buildMessage(text) {
return buildGenericMessage(TITLE_MESSAGE, 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 = { module.exports = {
tinytom,
buildErrorMessage, buildErrorMessage,
buildMessage, buildMessage,
//For testing //For testing

View File

@ -21,5 +21,21 @@
"html": "¡Hola!, <br/><br/>Solicitaste cambiar la contraseña, hemos creado una nueva.<br/><br/>Entra en la app con tu dirección de email y esta clave: <b>{{password}}</b><br/><br/>Fundación Lo Que De Verdad Importa." "html": "¡Hola!, <br/><br/>Solicitaste cambiar la contraseña, hemos creado una nueva.<br/><br/>Entra en la app con tu dirección de email y esta clave: <b>{{password}}</b><br/><br/>Fundación Lo Que De Verdad Importa."
} }
} }
},
"push": {
"confirmInvitation": {
"title": "{{ congress }}",
"body": "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 }}"
}
}
}
} }
} }

View File

@ -0,0 +1,44 @@
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.body,
ttl: data.ttl,
priority: data.priority ? data.priority : 'high',
recipients: data.recipients,
data: data.data,
userId: data.userId,
}
}
createNotificationValidatedInscription = (inscription) => {
let jsonMessage = messages.push.confirmInvitation;
const jsonNotification = tinytom(jsonMessage, {
congress: inscription.event.name,
ticketId: inscription.id,
});
return {
...jsonNotification,
date: moment(),
priority: "high",
recipients: {
"userIds": [
inscription.user.id,
]
},
}
}
module.exports = {
createNotificationValidatedInscription,
createNotification,
//createMessageNotification
}

133
helpers/push.helper.js Normal file
View File

@ -0,0 +1,133 @@
const { Expo } = require('expo-server-sdk');
// Create a new Expo SDK client
const expo = new Expo();
const createPushMessage = (data) => {
const pushMessage = {
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',
channelId: 'lqdvi-messages'
};
console.log(pushMessage);
return pushMessage;
};
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);
});
}

View File

@ -77,7 +77,7 @@ module.exports = function (sequelize, DataTypes) {
User.Comments = User.hasMany(models.Comment, { foreignKey: 'userId' }); User.Comments = User.hasMany(models.Comment, { foreignKey: 'userId' });
User.EventsReservations = User.hasMany(models.EventReservation, { foreignKey: 'userId' }); User.EventsReservations = User.hasMany(models.EventReservation, { foreignKey: 'userId' });
User.EventsInscriptions = User.hasMany(models.EventInscription, { foreignKey: 'userId' }); User.EventsInscriptions = User.hasMany(models.EventInscription, { foreignKey: 'userId' });
User.Questions = User.hasMany(models.EventQuestion, { foreignKey: 'userId', as: "questions", required: false, }); User.Questions = User.hasMany(models.EventQuestion, { foreignKey: 'userId', as: "questions", required: false });
// User.InscriptionsValidate = User.hasMany(models.EventIncription, { foreignkey: 'validateUserId'}) // User.InscriptionsValidate = User.hasMany(models.EventIncription, { foreignkey: 'validateUserId'})
//User.Reactions = User.hasMany(models.UserReaction, { foreignKey: 'UserId' }); //User.Reactions = User.hasMany(models.UserReaction, { foreignKey: 'UserId' });

View File

@ -26,14 +26,13 @@ const extraMethods = {
}) })
}, },
_updateLastLoginAndVersionUser: async (Id, appVersion) => { _updateLastLoginAndVersionUser: async (id, appVersion) => {
return models.User.update ( return models.User.update ({
{ app_version : appVersion, app_version : appVersion,
lastlogin: moment().utc() }, lastlogin: moment().utc()
{ }, {
where: { id: Id} where: { id: id }
}, });
);
}, },
_getOrCreateUser: async (dataUser) => { _getOrCreateUser: async (dataUser) => {

View File

@ -4,11 +4,14 @@ const httpStatus = require('http-status');
const generateControllers = require('../../core/controllers'); const generateControllers = require('../../core/controllers');
const QRHelper = require('../../helpers/qr.helper'); const QRHelper = require('../../helpers/qr.helper');
const emailHelper = require('../../helpers/mail.helper'); const emailHelper = require('../../helpers/mail.helper');
const notificationHelper = require('../../helpers/notification.helpers');
const path = require("path"); const path = require("path");
const messages = require('../../helpers/messages.json'); const messages = require('../../helpers/messages.json');
const eventService = require('./event.service'); const eventService = require('./event.service');
const eventReservationService = require('./events_reservations.service'); const eventReservationService = require('./events_reservations.service');
const eventInscriptionService = require('./events_inscriptions.service'); const eventInscriptionService = require('./events_inscriptions.service');
const notificationService = require('../notification/notification.service');
const { extractParamsFromRequest, handleErrorResponse, handleResultResponse } = require('../../helpers/controller.helper'); const { extractParamsFromRequest, handleErrorResponse, handleResultResponse } = require('../../helpers/controller.helper');
//PRUEBA //PRUEBA
@ -26,25 +29,25 @@ function generateMemberInscription (user, inscription, reservation) {
let memberInscription = null; let memberInscription = null;
if (user && inscription) { if (user && inscription) {
memberInscription = { memberInscription = {
marketing_memberId: null, marketing_memberId: null,
email: user.email, email: user.email,
name: user.name, name: user.name,
surname: user.surname, surname: user.surname,
source: inscription.source, source: inscription.source,
event_name: (inscription.event) ? inscription.event.name : 'N/A', event_name: (inscription.event) ? inscription.event.name : 'N/A',
event_date: (inscription.event) ? inscription.event.init_date : 'N/A', event_date: (inscription.event) ? inscription.event.init_date : 'N/A',
reservation_code: (reservation) ? reservation.reservation_code : null, reservation_code: (reservation) ? reservation.reservation_code : null,
date_inscription: inscription.date, date_inscription: inscription.date,
code_ticket: inscription.code_ticket, code_ticket: inscription.code_ticket,
validated: inscription.validated, validated: inscription.validated,
color: (reservation) ? reservation.color : null, color: (reservation) ? reservation.color : null,
description: ((reservation) ? reservation.description : 'Entrada').toUpperCase(), description: ((reservation) ? reservation.description : 'Entrada').toUpperCase(),
entity: (reservation) ? reservation.Entity.name : user.entityId, entity: (reservation) ? reservation.Entity.name : user.entityId,
userId: user.id, userId: user.id,
qrConfig: null, qrConfig: null,
qrCode: null, qrCode: null,
} }
} }
return memberInscription; return memberInscription;
@ -264,19 +267,32 @@ console.log('>>>>>>>>>>>>>>><NewConfirmedEvent: ', NewConfirmedEvent);
//Eliminamos miembro de la lista de mailchimp a la que está asociado //Eliminamos miembro de la lista de mailchimp a la que está asociado
await eventInscriptionService._deleteMember(marketingListIdOverflow, inscription.marketing_memberId); await eventInscriptionService._deleteMember(marketingListIdOverflow, inscription.marketing_memberId);
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);
//Mandar correo de confirmacion de desinscripcion //Mandar correo de confirmacion de inscripcion
try { 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)) emailHelper.sendTicket(generateHeaderMail(member), generateBodyMail(member))
} catch (error) { } catch (error) {
console.log('No se ha podido mandar email con entrada'); console.log('No se ha podido mandar email con entrada');
}; };
try {
let notification = notificationHelper.createNotificationValidatedInscription(inscription);
console.log(notification);
let result = notificationService.sendNotification(notification, [inscription.user.id]);
console.log(result);
} catch (error) {
console.log('No se ha podido mandar push');
};
}; };
return handleResultResponse("Inscripción validada", null, params, res, httpStatus.OK); return handleResultResponse("Inscripción validada", null, params, res, httpStatus.OK);
@ -285,6 +301,7 @@ console.log('mandar correo>>>>>>>>>>>>>>>>>>>>> ', member);
return handleResultResponse("No se pudo validar inscripción", null, params, res, httpStatus.NOT_FOUND); return handleResultResponse("No se pudo validar inscripción", null, params, res, httpStatus.NOT_FOUND);
} catch (error) { } catch (error) {
console.log(error);
return handleResultResponse("Error al validar inscripción", null, params, res, httpStatus.NOT_FOUND); return handleResultResponse("Error al validar inscripción", null, params, res, httpStatus.NOT_FOUND);
} }

View File

@ -8,6 +8,8 @@ const notificationDetailService = require('./notification_detail.service');
const userDeviceService = require('./user_device.service'); const userDeviceService = require('./user_device.service');
const eventInscriptionService = require('../events/events_inscriptions.service'); const eventInscriptionService = require('../events/events_inscriptions.service');
const { usersIdsComposer } = require('../../helpers/composes.helper'); 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'); const { extractParamsFromRequest, handleErrorResponse, handleResultResponse } = require('../../helpers/controller.helper');
@ -17,77 +19,32 @@ const controllerOptions = { MODULE_NAME };
const extraControllers = { const extraControllers = {
sendNotificationEvent: async (req, res, next) => {
/**
* notificationSample = {
* "tittle": "título de la notificación",
* "message": "cuerpo de la notificación",
* "recipients": {
* "eventId": "xxx-xxx-xxx-xxx",
* "segment": "ALL" | "ALL_VALIDATED" | "ALL_NOT_VALIDATED" |
* "PARTNERS_ALL" | "PARTNERS_VALIDATED" | "PARTNERS_NOT_VALIDATED" |
* "COLLEGE_ALL" | "COLLEGE_VALIDATED" | "COLLEGE_NOT_VALIDATED"
* },
* "data": {
* "type": "message",
* "title": "Título del mensaje",
* "message": "Cuerpo del mensaje",
* "button": {
* "caption": "Etiqueta del boton",
* "url": "https://www.site.es",
* "screen": "<RouterName>",
* "paramId": "23",
* }
* }
*}
*/
let usersIds = null;
const params = req.body;
const eventId = params.recipients.eventId;
const segment = params.recipients.segment;
console.log('prueba de llamada>>>>> ', params);
try {
notificationService.createNotification(params.date, params.title, params.message, undefined, 'default', params.data, req.user.id);
switch (segment) {
//Todos los inscritos al evento tanto en validos como en lista de espera
case 'ALL':
usersIds = await eventInscriptionService._getInscriptionByEvent(eventId);
break;
//Todos los inscritos tanto invitados como libres
case 'ALL_VALIDATED':
usersIds = await eventInscriptionService._getInscriptionByEventAndValidated(eventId, true);
break;
//Todos los de lista de espera tanto invitados como libres (Actualmente en invitados no hay lista de espera)
case 'ALL_NOT_VALIDATED':
usersIds = await eventInscriptionService._getInscriptionByEventAndValidated(eventId, false);
break;
//Solo invitados como actualmente no se usa codigo de reserva para los coles, vale con filtrar por aquellos que tengan codigo de reserva
case 'PARTNERS_ALL':
usersIds = await eventInscriptionService._getInscriptionByEventFromPartner(eventId);
break;
//Todos los inscritos al evento, tanto validados como en lista de espera
default: //ALL
break;
}
usersIds = usersIdsComposer(usersIds);
console.log('usuarios inscritos>>>>> ', usersIds);
} catch(error) {
return handleErrorResponse(MODULE_NAME, 'sendNotificationEvent', error, res);
}
return handleResultResponse("sendNotificationEvent", null, params, res, httpStatus.OK);
},
sendNotification: (config) => { sendNotification: (config) => {
/**
* notificationSample = {
* "tittle": "título de la notificación",
* "message": "cuerpo de la notificación",
* "recipients": {
* "eventId": "xxx-xxx-xxx-xxx",
* "segment": "ALL" | "ALL_VALIDATED" | "ALL_NOT_VALIDATED" |
* "PARTNERS_ALL" | "PARTNERS_VALIDATED" | "PARTNERS_NOT_VALIDATED" |
* "COLLEGE_ALL" | "COLLEGE_VALIDATED" | "COLLEGE_NOT_VALIDATED"
* },
* "data": {
* "type": "message",
* "title": "Título del mensaje",
* "message": "Cuerpo del mensaje",
* "button": {
* "caption": "Etiqueta del boton",
* "url": "https://www.site.es",
* "screen": "<RouterName>",
* "paramId": "23",
* }
* }
*}
*/
return async (req, res, next) => { return async (req, res, next) => {
config = config || { config = config || {
scopes: [], scopes: [],
@ -97,115 +54,70 @@ const extraControllers = {
const context = buildContext(req, config); const context = buildContext(req, config);
let params = extractParamsFromRequest(req, res); let params = extractParamsFromRequest(req, res);
let userIds = req.body.userIds; let userIds = undefined;
if (!userIds) { let eventId = undefined;
return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing user Ids'), res) let segment = undefined;
} const { body } = req;
if (!req.body.title) { if (!body.title) {
return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing message title'), res) return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing message title'), res)
} }
if (!req.body.message) { if (!body.body) {
return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing message content'), res) return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing body content'), res)
} }
try { // Evento?
let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId }}, context).then(function(result) { if (body.recipients.eventId) {
return new Promise(function(resolve) { resolve(result.rows) }); eventId = body.recipients.eventId;
}); segment = body.recipients.segment;
let saveNotificationPromise = (notification) => notificationService.create(notification, context); } else if (body.recipients.userIds) {
let sendNotificationsPromise = (messages) => notificationService.sendNotification(messages); userIds = body.recipients.userIds;
let disableUserDevicePromise = (token) => userDeviceService.update({ params: { } else {
token: token, return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing user Ids or event Ids'), res)
}}, { valid: 0, invalidated: moment() }, context); }
let disableInvalidUserDevicesPromise = (userDevices) => {
return new Promise(function (resolve) {
let _userDevices = [];
userDevices.forEach(async function (userDevice) {
if (!userDeviceService.isValidPushToken(userDevice.token)) {
await disableUserDevicePromise(userDevice.token);
} else {
_userDevices.push(userDevice);
}
});
resolve(_userDevices) try {
}); let notification = notificationHelper.createNotification ({
}; ...body,
userId: context.user.id
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);
const notificationRecord = {
date: moment(),
title: req.body.title,
body: req.body.message,
ttl: req.body.ttl || undefined,
priority: req.body.priority || 'default',
data: req.body.data || { userIds: req.body.userIds },
userId: context.user.id,
};
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 getUserDevicesList = [];
userIds.forEach(function (userId) {
getUserDevicesList.push(getUserDevicesPromise(userId));
}); });
saveNotificationPromise(notificationRecord) let getUserIds = async () => {
.then(function(notification) { if (userIds) {
notificationRecord.id = notification.id; return new Promise(function(resolve) { resolve(userIds) } );
return Promise.all(getUserDevicesList) } else if (eventId && segment) {
}).then(function (userDeviceList) { switch (segment) {
return new Promise(function(resolve) { resolve(userDeviceList[0]); }); //Todos los inscritos tanto invitados como libres
}) case 'ALL_VALIDATED':
.then(disableInvalidUserDevicesPromise) userIds = await eventInscriptionService._getInscriptionByEventAndValidated(eventId, true);
.then(buildMessagesPromise) break;
.then(sendNotificationsPromise) //Todos los de lista de espera tanto invitados como libres (Actualmente en invitados no hay lista de espera)
.then(function({ messages, tickets }) { case 'ALL_NOT_VALIDATED':
let _saveStatus = saveResponseStatusPromise(messages, tickets); userIds = await eventInscriptionService._getInscriptionByEventAndValidated(eventId, false);
let _disableDevices = disableUserDevicesWithErrorStatus(messages, tickets); break;
//Solo invitados como actualmente no se usa codigo de reserva para los coles, vale con filtrar por aquellos que tengan codigo de reserva
case 'PARTNERS_ALL':
userIds = await eventInscriptionService._getInscriptionByEventFromPartner(eventId);
break;
//Todos los inscritos al evento, tanto validados como en lista de espera
default: //ALL
userIds = await eventInscriptionService._getInscriptionByEvent(eventId);
break;
}
return new Promise(function (resolve) { resolve(usersIdsComposer(userIds)); });
} else {
return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing event and segment'), res)
}
}
receipt = notificationService.sendNotification(notification, await getUserIds());
return Promise.all([_saveStatus, _disableDevices]);
})
.then(function (details) {
console.log(details);
});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', error, res) return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', error, res)

View File

@ -26,6 +26,10 @@ module.exports = function (sequelize, DataTypes) {
allowNull: false, allowNull: false,
default: 'default', default: 'default',
}, },
recipients: {
type: DataTypes.JSON,
allowNull: true,
},
data: { data: {
type: DataTypes.JSON, type: DataTypes.JSON,
allowNull: true, allowNull: true,

View File

@ -6,7 +6,7 @@ const PaginateMiddleware = require('../../middlewares/paginate');
const FieldMiddleware = require('../../middlewares/fields'); const FieldMiddleware = require('../../middlewares/fields');
const SortMiddleware = require('../../middlewares/sort'); const SortMiddleware = require('../../middlewares/sort');
const notificationController = require('./notification.controller'); const notificationController = require('./notification.controller');
const { pushSendEvent, deviceTokenInputType, notificationSendType } = require('./notification.validations'); const { pushSendEvent, deviceTokenInputType } = require('./notification.validations');
const generalInvalidFields = [ const generalInvalidFields = [
'createdAt', 'updatedAt', 'createdAt', 'updatedAt',
@ -36,17 +36,17 @@ routes.get('/admin/notifications/:id',
routes.post('/admin/notifications', routes.post('/admin/notifications',
isAdministratorUser, isAdministratorUser,
SchemaValidator(notificationSendType, true), SchemaValidator(pushSendEvent, true),
notificationController.sendNotification({ notificationController.sendNotification({
scopes: ['defaultScope'] scopes: ['defaultScope']
}) })
); );
routes.post('/admin/notifications/event', /*routes.post('/admin/notifications/event',
isAdministratorUser, isAdministratorUser,
SchemaValidator(pushSendEvent, true), SchemaValidator(pushSendEvent, true),
notificationController.sendNotificationEvent notificationController.sendNotificationEvent
); );*/
/* Borrar cuando ya no aparezca la versión 1.0.10 */ /* Borrar cuando ya no aparezca la versión 1.0.10 */
routes.post('/notifications/register', routes.post('/notifications/register',

View File

@ -1,74 +1,137 @@
const { Expo } = require('expo-server-sdk'); const moment = require('moment');
const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper');
const models = require('../../core/models');
// Create a new Expo SDK client const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper');
const expo = new Expo(); 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 = { const extraMethods = {
createNotification: (date, title, body, ttl, priority, data, userId) => { createNotification: (data) => {
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<valores de la notificacion'); return {
console.log(title, body, ttl, priority, data, userId); date: data.date,
title: data.title,
body: data.body,
ttl: data.ttl,
priority: data.priority,
recipients: data.recipients,
data: data.data,
userId: data.userId,
}
},
return new Promise(function (resolve, reject) { saveNotification: ({ date, title, body, ttl, priority, recipients, data, userId }) => {
models.Notification.create({ return models.Notification.create({
date: date, date: moment(date),
title: title, title: title,
body: body, body: body,
userId: userId, userId: userId,
ttl: ttl, ttl: ttl ? ttl : undefined,
priority: priority, priority: priority ? priority : 'default',
data: data, recipients: recipients,
}) data: data,
.then(function (result) {
resolve(result);
})
.catch(function (error) {
reject(error)
});
}); });
}, },
sendNotification: (notification, userIds) => {
sendNotification: async (messages) => {
// The Expo push notification service accepts batches of notifications so console.log('sendNofitication -----------------------------------------------');
// that you don't need to send 1000 requests to send 1000 notifications. We console.log(notification, userIds);
// recommend you batch your notifications to reduce the number of requests
// and to compress them (notifications with similar content will get
// compressed).
/** let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId } }, {}).then(function (result) {
* There is a limit on the number of push notifications (100) you can send at once.Use return new Promise(function (resolve) { resolve(result.rows) });
* `chunkPushNotifications` to divide an array of push notification messages into appropriately });
* sized chunks 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 let disableInvalidUserDevicesPromise = (userDevices) => {
// notifications to Apple or Google (usually quickly, but allow the the service return new Promise(function (resolve) {
// up to 30 minutes when under load), a "receipt" for each notification is let _userDevices = [];
// created. The receipts will be available for at least a day; stale receipts userDevices.forEach(async function (userDevice) {
// are deleted. if (!userDeviceService.isValidPushToken(userDevice.token)) {
// await disableUserDevicePromise(userDevice.token);
// The ID of each receipt is sent back in the response "ticket" for each } else {
// notification. In summary, sending a notification produces a ticket, which _userDevices.push(userDevice);
// 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); resolve(_userDevices)
return { });
messages,
tickets: await _sendPushNotificationsAsync(chunks)
}; };
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) {
getUserDevicesList.push(getUserDevicesPromise(userId));
});
return Promise.all(getUserDevicesList)
}).then(function (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() => { getNotificationsWithoutReceipt: async() => {
}, },
@ -84,67 +147,4 @@ const extraMethods = {
module.exports = generateService(models.Notification, 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);
});
}

View File

@ -4,21 +4,24 @@ const deviceTokenInputType = Joi.object().keys({
token: Joi.string().required(), token: Joi.string().required(),
}); });
const pushSendType = Joi.object().keys({ /*const pushSendType = Joi.object().keys({
userIds: Joi.array().required(), userIds: Joi.array().required(),
title: Joi.string().required(), title: Joi.string().required(),
message: Joi.string().required(), message: Joi.string().required(),
//token: Joi.string().required(), //token: Joi.string().required(),
}); });*/
const pushSendEvent = Joi.object().keys({ const pushSendEvent = Joi.object().keys({
date: Joi.date().required(), date: Joi.date().optional(),
title: Joi.string().required(), title: Joi.string().required(),
message: Joi.string().required(), body: Joi.string().required(),
recipients: Joi.object().keys({ recipients: Joi.object().keys({
eventId: Joi.string().required(), userIds: Joi.array().optional(),
segment: Joi.string().required(), eventId: Joi.string().optional(),
segment: Joi.string().optional(),
}), }),
priority: Joi.string().optional(),
ttl: Joi.string().optional(),
data: Joi.object().keys({ data: Joi.object().keys({
type: Joi.string().required(), type: Joi.string().required(),
title: Joi.string().required(), title: Joi.string().required(),
@ -33,5 +36,5 @@ const pushSendEvent = Joi.object().keys({
}); });
module.exports = { module.exports = {
deviceTokenInputType, pushSendType, pushSendEvent deviceTokenInputType, pushSendEvent
}; };

View File

@ -66,6 +66,7 @@
"sanitize-filename": "^1.6.2", "sanitize-filename": "^1.6.2",
"sequelize": "^5.16.0", "sequelize": "^5.16.0",
"sharp": "^0.23.0", "sharp": "^0.23.0",
"tinytim": "^0.1.1",
"unique-file-name": "^1.0.0", "unique-file-name": "^1.0.0",
"vimeo": "^2.1.1", "vimeo": "^2.1.1",
"vm": "^0.1.0", "vm": "^0.1.0",