diff --git a/modules/auth/user.service.js b/modules/auth/user.service.js index 3948d0e..8892d31 100644 --- a/modules/auth/user.service.js +++ b/modules/auth/user.service.js @@ -3,10 +3,7 @@ const _ = require("lodash"); const moment = require("moment"); -const { - generateService, - parseParamsToFindOptions, -} = require("../../helpers/service.helper"); +const { generateService, parseParamsToFindOptions } = require("../../helpers/service.helper"); const models = require("../../core/models"); const Sequelize = require("sequelize"); moment.locale("es"); @@ -28,12 +25,13 @@ const extraMethods = { }); }, - _getActiveUserIds: async (offset = 0, limit = 10) => { + _getActiveUserIds: async (offset = 0, limit = 10, country = "ES") => { return models.User.findAndCountAll({ attributes: ["id"], where: { state: "active", phone: { [Sequelize.Op.ne]: null }, + country: country === "*" ? { [Sequelize.Op.ne]: null } : country, }, raw: true, limit: limit, diff --git a/modules/notification/notification.controller.js b/modules/notification/notification.controller.js index c261bd8..f9f286b 100644 --- a/modules/notification/notification.controller.js +++ b/modules/notification/notification.controller.js @@ -1,218 +1,240 @@ -'use strict'; -const httpStatus = require('http-status'); -const moment = require('moment'); -const generateControllers = require('../../core/controllers'); -const { buildContext } = require('../../core/controllers'); -const notificationService = require('./notification.service'); -const notificationDetailService = require('./notification_detail.service'); -const userService = require('../auth/user.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') +"use strict"; +const httpStatus = require("http-status"); +const moment = require("moment"); +const generateControllers = require("../../core/controllers"); +const { buildContext } = require("../../core/controllers"); +const notificationService = require("./notification.service"); +const notificationDetailService = require("./notification_detail.service"); +const userService = require("../auth/user.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'); -const { get } = require('lodash'); +const { + extractParamsFromRequest, + handleErrorResponse, + handleResultResponse, +} = require("../../helpers/controller.helper"); +const { get } = require("lodash"); // Module Name -const MODULE_NAME = '[notification.controller]'; +const MODULE_NAME = "[notification.controller]"; const controllerOptions = { MODULE_NAME }; - async function _sendNotificationAllActiveUsers(notification) { - let limit = 33; - let page = 1; + let limit = 33; + let page = 1; - let totalRows = 0; - let totalPages = -1; + let totalRows = 0; + let totalPages = -1; - do { - let offset = (page - 1) * limit; - let result = await userService._getActiveUserIds(offset, limit); + do { + let offset = (page - 1) * limit; + let result = await userService._getActiveUserIds(offset, limit, notification.recipients.country); - if (totalPages == -1) { - totalPages = Math.ceil(result.count / limit); - } + if (totalPages == -1) { + totalPages = Math.ceil(result.count / limit); + } - page = page + 1; + page = page + 1; - let ids = result.rows.map(function (item) { return item.id }); - notificationService.sendNotificationToUsers(notification, ids); + let ids = result.rows.map(function (item) { + return item.id; + }); + notificationService.sendNotificationToUsers(notification, ids); + } while (page <= totalPages); - - } while (page <= totalPages); - - return 'OK'; + return "OK"; } - async function _sendNotificationAllActiveDevices(notification) { - let limit = 33; - let page = 1; + let limit = 33; + let page = 1; - let totalRows = 0; - let totalPages = -1; + let totalRows = 0; + let totalPages = -1; - do { - let offset = (page - 1) * limit; - let result = await userDeviceService._getActiveDeviceIds(offset, limit); + do { + let offset = (page - 1) * limit; + let result = await userDeviceService._getActiveDeviceIds(offset, limit, notification.recipients.country); - console.log(result); + console.log(result); - if (totalPages == -1) { - totalPages = Math.ceil(result.count / limit); - } + if (totalPages == -1) { + totalPages = Math.ceil(result.count / limit); + } - page = page + 1; + page = page + 1; - notificationService.sendNotificationToDevices(notification, result.rows); - } while (page <= totalPages); + notificationService.sendNotificationToDevices(notification, result.rows); + } while (page <= totalPages); - return 'OK'; + return "OK"; } async function _getUserIdsForEventId(eventId, segment) { - let userIds = []; + let userIds = []; - switch (segment) { - //Todos los inscritos tanto invitados como libres - case 'ALL_VALIDATED': - userIds = 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': - userIds = await eventInscriptionService._getInscriptionByEventAndValidated(eventId, false); - break; + switch (segment) { + //Todos los inscritos tanto invitados como libres + case "ALL_VALIDATED": + userIds = 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": + userIds = 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': - userIds = await eventInscriptionService._getInscriptionByEventFromPartner(eventId); - 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; - } + //Todos los inscritos al evento, tanto validados como en lista de espera + default: //ALL + userIds = await eventInscriptionService._getInscriptionByEvent(eventId); + break; + } - return usersIdsComposer(userIds); + return usersIdsComposer(userIds); } - const extraControllers = { + sendNotification: (config) => { + /** + * notificationSample = { + * "title": "título de la notificación", + * "body": "cuerpo de la notificación", + * "recipients": { + * OPCION 1- Unos usuarios determinados + * "userIds": ["*" | "f428a317-6d1f-4eda-aa3e-22baff3f48d7", ...] + * "segment": "ALL" | "LAST_YEAR" | "LAST_MONTH" + * "country": "*" | "ES" | "MX" + * + * OPCION 2 - A todos los usuarios inscritos a un evento, se puede segmentar + * "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" + * OPCION 3 - A todos los dispositivos activos + * "deviceIds": ["*" | "ExponentPushToken[YbOS1AIZjQbchZbxNaVRqC]", ...] + * "country": "*" | "ES" | "MX" + * }, + * "data": { + * "type": "message", + * "title": "Título del mensaje", + * "message": "Cuerpo del mensaje", + * "button": { + * "caption": "Etiqueta del boton", + * "url": "https://www.site.es", + * "screen": "", + * "paramId": "23", + * } + * } + *} + */ - sendNotification: (config) => { + return async (req, res, next) => { + config = config || { + scopes: [], + }; - /** - * notificationSample = { - * "title": "título de la notificación", - * "body": "cuerpo de la notificación", - * "recipients": { - * OPCION 1- Unos usuarios determinados - * "userIds": ["*" | "f428a317-6d1f-4eda-aa3e-22baff3f48d7", ...] - * "segment": "ALL" | "LAST_YEAR" | "LAST_MONTH" - * - * OPCION 2 - A todos los usuarios inscritos a un evento, se puede segmentar - * "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" - * OPCION 3 - A todos los dispositivos activos - * "deviceIds": ["*" | "ExponentPushToken[YbOS1AIZjQbchZbxNaVRqC]", ...] - * }, - * "data": { - * "type": "message", - * "title": "Título del mensaje", - * "message": "Cuerpo del mensaje", - * "button": { - * "caption": "Etiqueta del boton", - * "url": "https://www.site.es", - * "screen": "", - * "paramId": "23", - * } - * } - *} - */ + const context = buildContext(req, config); + let params = extractParamsFromRequest(req, res); - return async (req, res, next) => { - config = config || { - scopes: [], - }; + let receipt = undefined; + let userIds = undefined; + let deviceIds = undefined; + let eventId = undefined; + let segment = undefined; + let country = undefined; - const context = buildContext(req, config); - let params = extractParamsFromRequest(req, res); + const { body } = req; - let receipt = undefined; - let userIds = undefined; - let deviceIds = undefined; - let eventId = undefined; - let segment = undefined; - const { body } = req; + if (!body.title) { + return handleErrorResponse( + controllerOptions.MODULE_NAME, + "sendNotification", + new Error("Missing message title"), + res + ); + } - if (!body.title) { - return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing message title'), res) - } + if (!body.body) { + return handleErrorResponse( + controllerOptions.MODULE_NAME, + "sendNotification", + new Error("Missing body content"), + res + ); + } - if (!body.body) { - return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing body content'), res) - } + // Evento? + if (body.recipients.eventId) { + eventId = body.recipients.eventId; + segment = body.recipients.segment; + } else if (body.recipients.userIds) { + userIds = body.recipients.userIds; + segment = body.recipients.segment; + country = body.recipients.country; + } else if (body.recipients.deviceIds) { + deviceIds = body.recipients.deviceIds; + country = body.recipients.country; + } else { + return handleErrorResponse( + controllerOptions.MODULE_NAME, + "sendNotification", + new Error("Missing user Ids or event Ids or devices Ids"), + res + ); + } + try { + let notification = notificationHelper.createNotification({ + ...body, + userId: context.user.id, + }); - // Evento? - if (body.recipients.eventId) { - eventId = body.recipients.eventId; - segment = body.recipients.segment; - } else if (body.recipients.userIds) { - userIds = body.recipients.userIds; - segment = body.recipients.segment; - } else if (body.recipients.deviceIds) { - deviceIds = body.recipients.deviceIds; + if (userIds && userIds.length == 1 && userIds[0] == "*") { + receipt = _sendNotificationAllActiveUsers(notification); + } else if (deviceIds && deviceIds.length == 1 && deviceIds[0] == "*") { + receipt = _sendNotificationAllActiveDevices(notification); + } else { + let _userIds = null; + + if (userIds) { + _userIds = userIds; + } else { + if (eventId) { + _userIds = await _getUserIdsForEventId(eventId, segment); } else { - return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing user Ids or event Ids or devices Ids'), res) + return handleErrorResponse( + controllerOptions.MODULE_NAME, + "sendNotification", + new Error("Missing event and segment"), + res + ); } + } - - try { - let notification = notificationHelper.createNotification({ - ...body, - userId: context.user.id - }); - - if ((userIds) && (userIds.length == 1) && (userIds[0] == "*")) { - receipt = _sendNotificationAllActiveUsers(notification); - } else if ((deviceIds) && (deviceIds.length == 1) && (deviceIds[0] == "*")) { - receipt = _sendNotificationAllActiveDevices(notification); - } else { - - let _userIds = null; - - if (userIds) { - _userIds = userIds; - } else { - if (eventId) { - _userIds = await _getUserIdsForEventId(eventId, segment); - } else { - return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', new Error('Missing event and segment'), res) - } - } - - if (_userIds) { - receipt = notificationService.sendNotificationToUsers(notification, _userIds); - } - } - - return handleResultResponse(receipt, null, null, res, httpStatus.OK); - } catch (error) { - console.error(error); - return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', error, res) - } + if (_userIds) { + receipt = notificationService.sendNotificationToUsers(notification, _userIds); + } } - }, - updateNotificationsWithReceipts: (config) => { - return async (req, res, next) => { - /*config = config || { + return handleResultResponse(receipt, null, null, res, httpStatus.OK); + } catch (error) { + console.error(error); + return handleErrorResponse(controllerOptions.MODULE_NAME, "sendNotification", error, res); + } + }; + }, + + updateNotificationsWithReceipts: (config) => { + return async (req, res, next) => { + /*config = config || { scopes: [], }; @@ -232,56 +254,56 @@ const extraControllers = { }) .then(sendNotificationsPromise) */ + }; + }, + + registerDevice: (config) => { + return async (req, res, next) => { + config = config || { + scopes: [], + }; + + try { + const context = buildContext(req, config); + const userId = context.user && context.user.id ? context.user.id : null; + + let data = { + token: req.body.token, + valid: 1, + country: req.body.country, + userId, + }; + + let params = extractParamsFromRequest(req, res, { + includeAll: false, + paginate: { limit: 1, page: 1 }, + params: { + token: data.token, + }, + }); + + console.log(req.body, data, params); + + // Buscamos el token y el usuario + console.log(">> Busco el token", params.params); + let result = await userDeviceService.fetchOne(params, context); + + if (result) { + // Borramos el registro donde aparece el token + console.log(">> Borro el registro del token", params.params, data, context); + result = await userDeviceService.delete(params, context); } - }, - registerDevice: (config) => { - return async (req, res, next) => { - config = config || { - scopes: [], - }; - - - try { - const context = buildContext(req, config); - const userId = context.user && context.user.id ? context.user.id : null; - - - let data = { - token: req.body.token, - valid: 1, - userId - }; - - - let params = extractParamsFromRequest(req, res, { - includeAll: false, - paginate: { limit: 1, page: 1 }, - params: { - token: data.token, - } - }); - - // Buscamos el token y el usuario - console.log('>> Busco el token', params.params); - let result = await userDeviceService.fetchOne(params, context); - - if (result) { - // Borramos el registro donde aparece el token - console.log('>> Borro el registro del token', params.params, data, context); - result = await userDeviceService.delete(params, context); - } - - // Dar de alta el token - console.log('>> Dar de alta el token', data); - result = await userDeviceService.create(data, context); - } catch (error) { - console.error(error); - } - // En todo caso devolver OK al cliente - return handleResultResponse('OK', null, null, res, httpStatus.OK); - } - }, + // Dar de alta el token + console.log(">> Dar de alta el token", data); + result = await userDeviceService.create(data, context); + } catch (error) { + console.error(error); + } + // En todo caso devolver OK al cliente + return handleResultResponse("OK", null, null, res, httpStatus.OK); + }; + }, }; -module.exports = generateControllers(notificationService, extraControllers, controllerOptions); \ No newline at end of file +module.exports = generateControllers(notificationService, extraControllers, controllerOptions); diff --git a/modules/notification/notification.validations.js b/modules/notification/notification.validations.js index 1e5a6d6..20378dc 100644 --- a/modules/notification/notification.validations.js +++ b/modules/notification/notification.validations.js @@ -1,7 +1,8 @@ -const Joi = require('joi'); +const Joi = require("joi"); const deviceTokenInputType = Joi.object().keys({ - token: Joi.string().required(), + token: Joi.string().required(), + country: Joi.string().optional(), }); /*const pushSendType = Joi.object().keys({ @@ -12,30 +13,32 @@ const deviceTokenInputType = Joi.object().keys({ });*/ const pushSendEvent = Joi.object().keys({ - date: Joi.date().optional(), + date: Joi.date().optional(), + title: Joi.string().required(), + body: Joi.string().required(), + recipients: Joi.object().keys({ + userIds: Joi.array().optional(), + deviceIds: Joi.array().optional(), + eventId: Joi.string().optional(), + segment: Joi.string().optional(), + country: Joi.string().optional(), + }), + priority: Joi.string().optional(), + ttl: Joi.string().optional(), + data: Joi.object().keys({ + type: Joi.string().required(), title: Joi.string().required(), - body: Joi.string().required(), - recipients: Joi.object().keys({ - userIds: Joi.array().optional(), - deviceIds: Joi.array().optional(), - eventId: Joi.string().optional(), - segment: Joi.string().optional(), - }), - priority: Joi.string().optional(), - ttl: Joi.string().optional(), - data: Joi.object().keys({ - type: Joi.string().required(), - title: Joi.string().required(), - message: Joi.string().required(), - button: Joi.object().keys({ - caption: Joi.string().required(), - url: Joi.string().optional(), - screen: Joi.string().optional(), - paramId: Joi.string().optional(), - }), + message: Joi.string().required(), + button: Joi.object().keys({ + caption: Joi.string().required(), + url: Joi.string().optional(), + screen: Joi.string().optional(), + paramId: Joi.string().optional(), }), + }), }); module.exports = { - deviceTokenInputType, pushSendEvent + deviceTokenInputType, + pushSendEvent, }; diff --git a/modules/notification/user_device.model.js b/modules/notification/user_device.model.js index 89a82f2..f212d9a 100644 --- a/modules/notification/user_device.model.js +++ b/modules/notification/user_device.model.js @@ -1,37 +1,44 @@ module.exports = function (sequelize, DataTypes) { - const UserDevice = sequelize.define('UserDevice', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - token: { - type: DataTypes.STRING, - allowNull: false, - }, - valid: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - invalidated: { - type: DataTypes.DATE, - }, - }, { - tableName: 'users_devices', - freezeTableName: true, - timestamps: true, + const UserDevice = sequelize.define( + "UserDevice", + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + token: { + type: DataTypes.STRING, + allowNull: false, + }, + valid: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + invalidated: { + type: DataTypes.DATE, + }, + country: { + type: DataTypes.STRING, + defaultValue: "ES", + }, + }, + { + tableName: "users_devices", + freezeTableName: true, + timestamps: true, - defaultScope: { - where: { - valid: true, - }, - }, - }); + defaultScope: { + where: { + valid: true, + }, + }, + } + ); - UserDevice.associate = function (models) { - UserDevice.User = UserDevice.belongsTo(models.User, { foreignKey: 'userId', as: "user" }); - }; + UserDevice.associate = function (models) { + UserDevice.User = UserDevice.belongsTo(models.User, { foreignKey: "userId", as: "user" }); + }; - return UserDevice; + return UserDevice; }; - \ No newline at end of file diff --git a/modules/notification/user_device.service.js b/modules/notification/user_device.service.js index 182b1be..092d409 100644 --- a/modules/notification/user_device.service.js +++ b/modules/notification/user_device.service.js @@ -1,44 +1,44 @@ -const moment = require('moment'); -const { Expo } = require('expo-server-sdk'); -const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper'); -const models = require('../../core/models'); +const moment = require("moment"); +const { Expo } = require("expo-server-sdk"); +const { generateService, parseParamsToFindOptions } = require("../../helpers/service.helper"); +const models = require("../../core/models"); +const { Sequelize } = require("sequelize"); const extraMethods = { - isValidPushToken: (token) => { - return Expo.isExpoPushToken(token); - }, + isValidPushToken: (token) => { + return Expo.isExpoPushToken(token); + }, - afterFetchAll: (result, params, context) => { + afterFetchAll: (result, params, context) => { + if (!result.count) { + return result; + } - if (!result.count) { - return result; - } + let rows = result.rows.map((row) => row.toJSON()); - let rows = result.rows.map(row => row.toJSON()); + return { + count: result.count, + rows: rows, + }; + }, - return { - count: result.count, - rows: rows - } - }, + getPushToken: (params) => { + return models.UserDevice.findOne({ + where: params, + }); + }, - - getPushToken: (params) => { - return models.UserDevice.findOne({ - where: params, - }); - }, - - _getActiveDeviceIds: async (offset = 0, limit = 10) => { - return models.UserDevice.findAndCountAll({ - where: { - valid: 1, - }, - raw: true, - limit: limit, - offset: offset, - }); + _getActiveDeviceIds: async (offset = 0, limit = 10, country = "ES") => { + return models.UserDevice.findAndCountAll({ + where: { + valid: 1, + country: country === "*" ? { [Sequelize.Op.ne]: null } : country, }, + raw: true, + limit: limit, + offset: offset, + }); + }, }; module.exports = generateService(models.UserDevice, extraMethods);