habitica-self-host/website/server/controllers/api-v3/pushNotifications.js

156 lines
4.6 KiB
JavaScript

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'));
}
// Use simple built-in defaults to avoid missing translation strings.
const notificationTitle = req.body?.title || 'Test Successful';
const notificationMessage = req.body?.message || 'This is a test UnifiedPush notification from Habitica.';
const successMessage = '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;