import { authWithHeaders } from '../../middlewares/auth'; import { NotFound, } from '../../libs/errors'; import { model as PushDevice } from '../../models/pushDevice'; import { sendNotification as sendPushNotification } from '../../libs/pushNotifications'; const api = {}; /** * @apiIgnore * @api {post} /api/v3/user/push-devices Add a push device to a user * @apiName UserAddPushDevice * @apiGroup User * * @apiParam (Body) {String} regId The id of the push device * @apiParam (Body) {String} type The type of push device * * @apiSuccess {Object} data List of push devices * @apiSuccess {String} message Success message */ api.addPushDevice = { method: 'POST', url: '/user/push-devices', middlewares: [authWithHeaders()], async handler (req, res) { const { user } = res.locals; req.checkBody('regId', res.t('regIdRequired')).notEmpty(); req.checkBody('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android', 'unifiedpush']); const validationErrors = req.validationErrors(); if (validationErrors) throw validationErrors; const { pushDevices } = user; const item = { regId: req.body.regId, type: req.body.type, }; // When adding a duplicate push device, fail silently instead of throwing an error if (pushDevices.find(device => device.regId === item.regId)) { res.respond(200, user.pushDevices, res.t('pushDeviceAdded')); return; } // Concurrency safe update const pushDevice = (new PushDevice(item)).toJSON(); // Create a mongo doc await user.updateOne({ $push: { pushDevices: pushDevice }, }).exec(); // Update the response user.pushDevices.push(pushDevice); res.respond(200, user.pushDevices, res.t('pushDeviceAdded')); }, }; /** * @apiIgnore * @api {post} /api/v3/user/push-devices/test Send a test push notification * @apiName UserSendTestPushNotification * @apiGroup User * * @apiParam (Body) {String} [regId] The id of a specific push device to target * * @apiSuccess {String} message Success message */ api.sendUnifiedPushTest = { method: 'POST', url: '/user/push-devices/test', middlewares: [authWithHeaders()], async handler (req, res) { const { user } = res.locals; const regId = req.body?.regId; const pushDevices = user.pushDevices?.toObject ? user.pushDevices.toObject() : user.pushDevices; let unifiedPushDevices = (pushDevices || []).filter(device => device?.type === 'unifiedpush'); if (regId) { unifiedPushDevices = unifiedPushDevices.filter(device => device.regId === regId); } if (unifiedPushDevices.length === 0) { throw new NotFound(res.t('pushDeviceNotFound')); } const notificationTitle = req.body?.title || res.t('unifiedPushTestTitle', { defaultValue: 'Habitica UnifiedPush Test' }); const notificationMessage = req.body?.message || res.t('unifiedPushTestMessage', { defaultValue: 'This is a test UnifiedPush notification from Habitica.' }); const successMessage = res.t('unifiedPushTestSent', { defaultValue: 'UnifiedPush test notification sent.' }); const userForPush = user.toObject ? user.toObject() : { ...user }; userForPush._id = user._id; userForPush.pushDevices = unifiedPushDevices; await sendPushNotification(userForPush, { identifier: 'unifiedPushTestNotification', title: notificationTitle, message: notificationMessage, payload: { message: notificationMessage, }, }); res.respond(200, null, successMessage); }, }; /** * @apiIgnore * @api {delete} /api/v3/user/push-devices/:regId remove a push device from a user * @apiName UserRemovePushDevice * @apiGroup User * * @apiParam (Path) {String} regId The id of the push device * * @apiSuccess {Object} data List of push devices * @apiSuccess {String} message Success message */ api.removePushDevice = { method: 'DELETE', url: '/user/push-devices/:regId', middlewares: [authWithHeaders()], async handler (req, res) { const { user } = res.locals; req.checkParams('regId', res.t('regIdRequired')).notEmpty(); const validationErrors = req.validationErrors(); if (validationErrors) throw validationErrors; const { regId } = req.params; const { pushDevices } = user; const indexOfPushDevice = pushDevices.findIndex(element => element.regId === regId); if (indexOfPushDevice === -1) { throw new NotFound(res.t('pushDeviceNotFound')); } // Concurrency safe update const pullQuery = { $pull: { pushDevices: { regId } } }; await user.updateOne(pullQuery).exec(); // Update the response pushDevices.splice(indexOfPushDevice, 1); res.respond(200, user.pushDevices, res.t('pushDeviceRemoved')); }, }; export default api;