diff --git a/connect/config.js b/connect/config.js index 057dbed..407761b 100644 --- a/connect/config.js +++ b/connect/config.js @@ -8,13 +8,6 @@ module.exports = { TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || 'https://api.topcoder-dev.com/v4', MESSAGE_API_BASE_URL: process.env.MESSAGE_API_BASE_URL || 'https://api.topcoder-dev.com/v5', - // Probably temporary variables for TopCoder role ids for 'Connect Manager', 'Connect Copilot' and 'administrator' - // These are values for development backend. For production backend they may be different. - // These variables are currently being used to retrieve above role members using API V3 `/roles` endpoint. - // As soon as this endpoint is replaced with more suitable one, these variables has to be removed if no need anymore. - CONNECT_MANAGER_ROLE_ID: 8, - CONNECT_COPILOT_ROLE_ID: 4, - ADMINISTRATOR_ROLE_ID: 1, // id of the BOT user which creates post with various events in discussions TCWEBSERVICE_ID: process.env.TCWEBSERVICE_ID || '22838965', CODERBOT_USER_ID: process.env.CODERBOT_USER_ID || 'CoderBot', diff --git a/connect/connectNotificationServer.js b/connect/connectNotificationServer.js index 7fa03f9..d581e09 100644 --- a/connect/connectNotificationServer.js +++ b/connect/connectNotificationServer.js @@ -11,7 +11,6 @@ const _ = require('lodash'); const service = require('./service'); const { BUS_API_EVENT } = require('./constants'); const EVENTS = require('./events-config').EVENTS; -const TOPCODER_ROLE_RULES = require('./events-config').TOPCODER_ROLE_RULES; const PROJECT_ROLE_RULES = require('./events-config').PROJECT_ROLE_RULES; const PROJECT_ROLE_OWNER = require('./events-config').PROJECT_ROLE_OWNER; const emailNotificationServiceHandler = require('./notificationServices/email').handler; @@ -30,7 +29,7 @@ const getTopCoderMembersNotifications = (eventConfig) => { } const getRoleMembersPromises = eventConfig.topcoderRoles.map(topcoderRole => ( - service.getRoleMembers(TOPCODER_ROLE_RULES[topcoderRole].id) + service.getRoleMembers(topcoderRole) )); return Promise.all(getRoleMembersPromises).then((membersPerRole) => { @@ -101,6 +100,34 @@ const getNotificationsForMentionedUser = (eventConfig, content) => { }); }; +/** + * Get notifications for users obtained from originator + * + * @param {Object} eventConfig event configuration + * @param {String} originator originator userId + * + * @return {Promise} resolves to a list of notifications + */ +const getNotificationsForOriginator = (eventConfig, originator) => { + // if event doesn't have to be notified to originator, just ignore + if (!eventConfig.originator) { + return Promise.resolve([]); + } + + // if we have to send notification to the originator, + // but it's not provided in the message, then throw error + if (!originator) { + return Promise.reject(new Error('Missing originator in the event message.')); + } + + return Promise.resolve([{ + userId: originator.toString(), + contents: { + originator: true, + }, + }]); +}; + /** * Get project members notifications * @@ -307,24 +334,28 @@ const handler = (topic, message, logger, callback) => { // - check that event has everything required or throw error getNotificationsForTopicStarter(eventConfig, message.topicId), getNotificationsForUserId(eventConfig, message.userId), + getNotificationsForOriginator(eventConfig, message.originator), getNotificationsForMentionedUser(eventConfig, message.postContent), getProjectMembersNotifications(eventConfig, project), getTopCoderMembersNotifications(eventConfig), - ]).then((notificationsPerSource) => ( + ]).then((notificationsPerSource) => { // first found notification for one user will be send, the rest ignored // NOTE all userId has to be string - _.uniqBy(_.flatten(notificationsPerSource), 'userId') - )).then((notifications) => ( + logger.debug('all notifications: ', notificationsPerSource); + return _.uniqBy(_.flatten(notificationsPerSource), 'userId'); + }).then((notifications) => ( excludeNotifications(notifications, eventConfig, message, { project, }) )).then((notifications) => { allNotifications = _.filter(notifications, notification => notification.userId !== `${message.initiatorUserId}`); - if (eventConfig.includeUsers && message[eventConfig.includeUsers] && message[eventConfig.includeUsers].length>0){ - allNotifications = _.filter(allNotifications, notification => message[eventConfig.includeUsers].contains(notification.userId)); + if (eventConfig.includeUsers && message[eventConfig.includeUsers] && + message[eventConfig.includeUsers].length > 0) { + allNotifications = _.filter(allNotifications, + notification => message[eventConfig.includeUsers].includes(notification.userId)); } - + logger.debug('filtered notifications: ', allNotifications); // now let's retrieve some additional data // if message has userId such messages will likely need userHandle and user full name diff --git a/connect/constants.js b/connect/constants.js index 5625c62..bfcf725 100644 --- a/connect/constants.js +++ b/connect/constants.js @@ -30,6 +30,9 @@ module.exports = { ASSIGNED_AS_OWNER: 'notifications.connect.project.member.assignedAsOwner', INVITE_CREATED: 'notifications.connect.project.member.invite.created', INVITE_UPDATED: 'notifications.connect.project.member.invite.updated', + INVITE_REQUESTED: 'notifications.connect.project.member.invite.requested', + INVITE_APPROVED: 'notifications.connect.project.member.invite.approved', + INVITE_REJECTED: 'notifications.connect.project.member.invite.rejected', }, PROJECT: { ACTIVE: 'notifications.connect.project.active', diff --git a/connect/events-config.js b/connect/events-config.js index 98b2cdb..6545783 100644 --- a/connect/events-config.js +++ b/connect/events-config.js @@ -1,7 +1,6 @@ /** * Configuration of connect events */ -const config = require('./config'); const { BUS_API_EVENT } = require('./constants'); // project member role names @@ -9,27 +8,24 @@ const PROJECT_ROLE_OWNER = 'owner'; const PROJECT_ROLE_COPILOT = 'copilot'; const PROJECT_ROLE_MANAGER = 'manager'; const PROJECT_ROLE_MEMBER = 'member'; +const PROJECT_ROLE_ACCOUNT_MANAGER = 'account_manager'; // project member role rules const PROJECT_ROLE_RULES = { [PROJECT_ROLE_OWNER]: { role: 'customer', isPrimary: true }, [PROJECT_ROLE_COPILOT]: { role: 'copilot' }, [PROJECT_ROLE_MANAGER]: { role: 'manager' }, + [PROJECT_ROLE_ACCOUNT_MANAGER]: { role: 'account_manager' }, [PROJECT_ROLE_MEMBER]: {}, }; // TopCoder roles const ROLE_CONNECT_COPILOT = 'Connect Copilot'; const ROLE_CONNECT_MANAGER = 'Connect Manager'; +const ROLE_CONNECT_COPILOT_MANAGER = 'Connect Copilot Manager'; +const ROLE_CONNECT_ACCOUNT_MANAGER = 'Connect Account Manager'; const ROLE_ADMINISTRATOR = 'administrator'; -// TopCoder role rules -const TOPCODER_ROLE_RULES = { - [ROLE_CONNECT_COPILOT]: { id: config.CONNECT_COPILOT_ROLE_ID }, - [ROLE_CONNECT_MANAGER]: { id: config.CONNECT_MANAGER_ROLE_ID }, - [ROLE_ADMINISTRATOR]: { id: config.ADMINISTRATOR_ROLE_ID }, -}; - /** * Supported events configuration * @@ -51,13 +47,14 @@ const EVENTS = [ { type: BUS_API_EVENT.CONNECT.PROJECT.CREATED, projectRoles: [PROJECT_ROLE_OWNER], + topcoderRoles: [ROLE_CONNECT_ACCOUNT_MANAGER], exclude: { topcoderRoles: [ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR], }, }, { type: BUS_API_EVENT.CONNECT.PROJECT.SUBMITTED_FOR_REVIEW, projectRoles: [PROJECT_ROLE_OWNER], - topcoderRoles: [ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR], + topcoderRoles: [ROLE_CONNECT_MANAGER, ROLE_CONNECT_ACCOUNT_MANAGER, ROLE_ADMINISTRATOR], }, { type: BUS_API_EVENT.CONNECT.PROJECT.APPROVED, projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER], @@ -107,6 +104,17 @@ const EVENTS = [ type: BUS_API_EVENT.CONNECT.MEMBER.INVITE_CREATED, projectRoles: [], toUserHandle: true, + }, { + type: BUS_API_EVENT.CONNECT.MEMBER.INVITE_REQUESTED, + topcoderRoles: [ROLE_CONNECT_COPILOT_MANAGER], + }, { + type: BUS_API_EVENT.CONNECT.MEMBER.INVITE_APPROVED, + toUserHandle: true, + originator: true, + }, { + type: BUS_API_EVENT.CONNECT.MEMBER.INVITE_REJECTED, + topcoderRoles: [ROLE_CONNECT_COPILOT_MANAGER], + originator: true, }, // Project activity @@ -149,7 +157,7 @@ const EVENTS = [ type: BUS_API_EVENT.CONNECT.PROJECT.FILE_UPLOADED, version: 2, projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER], - includeUsers: 'allowedUsers' + includeUsers: 'allowedUsers', }, { type: BUS_API_EVENT.CONNECT.PROJECT.SPECIFICATION_MODIFIED, version: 2, @@ -160,12 +168,12 @@ const EVENTS = [ }, { type: BUS_API_EVENT.CONNECT.PROJECT_PLAN.MODIFIED, projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER], - includeUsers: 'allowedUsers' + includeUsers: 'allowedUsers', }, { type: BUS_API_EVENT.CONNECT.PROJECT_PLAN.PROGRESS_UPDATED, projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER], }, - + // Phase activity { type: BUS_API_EVENT.CONNECT.PROJECT_PLAN.PHASE_ACTIVATED, @@ -200,8 +208,8 @@ const EVENTS = [ }, { type: BUS_API_EVENT.CONNECT.PROJECT_PLAN.TIMELINE_ADJUSTED, projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER], - includeUsers: 'allowedUsers' - } + includeUsers: 'allowedUsers', + }, ]; const EVENT_BUNDLES = { @@ -263,6 +271,9 @@ const EVENT_BUNDLES = { BUS_API_EVENT.CONNECT.MEMBER.MANAGER_JOINED, BUS_API_EVENT.CONNECT.MEMBER.REMOVED, BUS_API_EVENT.CONNECT.MEMBER.INVITE_CREATED, + BUS_API_EVENT.CONNECT.MEMBER.INVITE_REQUESTED, + BUS_API_EVENT.CONNECT.MEMBER.INVITE_APPROVED, + BUS_API_EVENT.CONNECT.MEMBER.INVITE_REJECTED, ], }, PROJECT_PLAN: { @@ -293,7 +304,6 @@ const EVENT_BUNDLES = { module.exports = { PROJECT_ROLE_RULES, - TOPCODER_ROLE_RULES, EVENTS, EVENT_BUNDLES, diff --git a/connect/notificationServices/email.js b/connect/notificationServices/email.js index 5aabd96..d184919 100644 --- a/connect/notificationServices/email.js +++ b/connect/notificationServices/email.js @@ -14,49 +14,48 @@ const { BUS_API_EVENT, SCHEDULED_EVENT_PERIOD, SETTINGS_EMAIL_SERVICE_ID, - ACTIVE_USER_STATUSES + ACTIVE_USER_STATUSES, } = require('../constants'); const { EVENTS, EVENT_BUNDLES } = require('../events-config'); const helpers = require('../helpers'); const service = require('../service'); -function replacePlaceholders(term,data){ +function replacePlaceholders(term, data) { let placeholders = term.match(/<[a-zA-Z]+>/g); let ret = term; - if (placeholders && placeholders.length){ - _(placeholders).each(p=>{ - let values = _.map(data,p.slice(1, -1)); + if (placeholders && placeholders.length) { + _(placeholders).each(p => { + let values = _.map(data, p.slice(1, -1)); const total = values.length; - let replacement = values.length < 3 ? - values.join(', ') : - values.slice(0,2).join(', ') +' and ' + (total-3) +'others'; + let replacement = values.length < 3 ? + values.join(', ') : + values.slice(0, 2).join(', ') + ' and ' + (total - 3) + 'others'; ret = ret.replace(p, values.join(', ')); - }) + }); } return ret; } -function getSections(projectUserEvents){ +function getSections(projectUserEvents) { let sections = []; _.chain(projectUserEvents) .groupBy(value => getEventGroupKey(value)) - .forIn((value,key)=>{ - if (!EVENT_BUNDLES[key].groupBy){ + .forIn((value, key) => { + if (!EVENT_BUNDLES[key].groupBy) { sections.push({ - title:replacePlaceholders(EVENT_BUNDLES[key].title,_(value).map(g=>g.data.data).value()), - [key]:true, - notifications: _(value).map(v=>v.data.data).value() + title: replacePlaceholders(EVENT_BUNDLES[key].title, _(value).map(g => g.data.data).value()), + [key]: true, + notifications: _(value).map(v => v.data.data).value(), }); } else { - _.chain(value).groupBy(n=>n.data.data[EVENT_BUNDLES[key].groupBy]).forIn((groupValue,groupKey)=>{ - + _.chain(value).groupBy(n => n.data.data[EVENT_BUNDLES[key].groupBy]).forIn((groupValue, groupKey) => { let title = EVENT_BUNDLES[key].title; - title = replacePlaceholders(title,_(groupValue).map(g=>g.data.data).value()); + title = replacePlaceholders(title, _(groupValue).map(g => g.data.data).value()); sections.push({ title, - [key]:true, - notifications: _(groupValue).map(g=>g.data.data).value() + [key]: true, + notifications: _(groupValue).map(g => g.data.data).value(), }); }).value(); } @@ -92,7 +91,7 @@ function handleScheduledEvents(events, setEventsStatus) { .mapValues(projectUserEvents => ({ id: _.get(projectUserEvents, '[0].data.data.projectId'), name: _.get(projectUserEvents, '[0].data.data.projectName'), - sections: getSections(projectUserEvents) + sections: getSections(projectUserEvents), })) .values() .value(), @@ -104,7 +103,7 @@ function handleScheduledEvents(events, setEventsStatus) { // update common values for bundled email eventMessage.replyTo = config.DEFAULT_REPLY_EMAIL; - eventMessage.version="v3"; + eventMessage.version = 'v3'; eventMessage.cc = []; eventMessage.from = { name: config.REPLY_EMAIL_FROM, @@ -145,8 +144,8 @@ function getEventGroupKey(value) { .keys() .find(key => _.includes(_.get(EVENT_BUNDLES, `${key}.types`), _.get(value, 'data.data.type'))) .value(); - if (!key) return 'DEFAULT'; - return key; + if (!key) return 'DEFAULT'; + return key; } /** @@ -157,7 +156,7 @@ function getEventGroupKey(value) { */ function wrapIndividualNotification(data) { const key = getEventGroupKey(data); - const subject = replacePlaceholders(EVENT_BUNDLES[key].subject,[data.data.data]); + const subject = replacePlaceholders(EVENT_BUNDLES[key].subject, [data.data.data]); return { subject, @@ -212,7 +211,7 @@ function handler(topicName, messageJSON, notification) { // don't send email notification for inactive users, ideally we should not have generated // notifications for inactive users, however, for now handling it here as safe gaurd if (userStatus && ACTIVE_USER_STATUSES.indexOf(userStatus) === -1) { - logger.error(`Notification generated for inactive user, ignoring`); + logger.error('Notification generated for inactive user, ignoring'); return; } if (config.ENABLE_DEV_MODE === 'true') { @@ -236,16 +235,16 @@ function handler(topicName, messageJSON, notification) { emailToAffectedUser: notification.contents.userEmail === userEmail, }, recipients, - version:"v3", + version: 'v3', from: { name: notification.contents.userHandle, email: config.DEFAULT_REPLY_EMAIL, }, categories, }; - eventMessage.data[eventMessage.data.type]=true; - _.assign(eventMessage.data,notification.contents); - + eventMessage.data[eventMessage.data.type] = true; + _.assign(eventMessage.data, notification.contents); + // default values that get overridden when the notification is about topics/posts updates let reference = 'project'; @@ -256,7 +255,7 @@ function handler(topicName, messageJSON, notification) { messagingEvent = true; eventMessage.data.topicId = parseInt(messageJSON.topicId, 10); eventMessage.data.postId = messageJSON.postId ? parseInt(messageJSON.postId, 10) : null; - if (messageJSON.postContent){ + if (messageJSON.postContent) { eventMessage.data.post = helpers.markdownToHTML(messageJSON.postContent); } @@ -304,9 +303,9 @@ function handler(topicName, messageJSON, notification) { // if we find the event category for the notification, use the bundle settings from the first event if (eventBundle && eventBundle.types && eventBundle.types.length) { const firstEvtInBundle = eventBundle.types[0]; - const firstEvtBundleSettingPath = `notifications['${firstEvtInBundle}'].${SETTINGS_EMAIL_SERVICE_ID}.bundlePeriod` + const firstEvtBundleSettingPath = `notifications['${firstEvtInBundle}'].${SETTINGS_EMAIL_SERVICE_ID}.bundlePeriod`; let firstEvtBundlePeriod = _.get(settings, firstEvtBundleSettingPath); - bundlePeriod = firstEvtBundlePeriod + bundlePeriod = firstEvtBundlePeriod; logger.debug('Assuming bundle period of first event in the event category=>', bundlePeriod); } } @@ -315,7 +314,7 @@ function handler(topicName, messageJSON, notification) { } logger.debug('bundlePeriod=>', bundlePeriod); - if (bundlePeriod && "immediately" !== bundlePeriod && !requiresImmediateAttention) { + if (bundlePeriod && 'immediately' !== bundlePeriod && !requiresImmediateAttention) { if (!SCHEDULED_EVENT_PERIOD[bundlePeriod]) { throw new Error(`User's '${notification.userId}' setting for service` + ` '${SETTINGS_EMAIL_SERVICE_ID}' option 'bundlePeriod' has unsupported value '${bundlePeriod}'.`); @@ -332,7 +331,7 @@ function handler(topicName, messageJSON, notification) { }); } else { // send single field "notificationsHTML" with the rendered template - eventMessage.data = wrapIndividualNotification({data:eventMessage}); + eventMessage.data = wrapIndividualNotification({ data: eventMessage }); console.log(eventMessage.data.contents); // send event to bus api diff --git a/connect/service.js b/connect/service.js index 850bb48..10a416c 100644 --- a/connect/service.js +++ b/connect/service.js @@ -6,6 +6,8 @@ const request = require('superagent'); const config = require('./config'); const _ = require('lodash'); +let rolesCache = null; + /** * Get project details * @@ -13,10 +15,10 @@ const _ = require('lodash'); * * @return {Promise} promise resolved to project details */ -const getProject = (projectId) => { - return M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) - .then((token) => { - return request +const getProject = (projectId) => ( + M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) + .then((token) => ( + request .get(`${config.TC_API_V4_BASE_URL}/projects/${projectId}`) .set('accept', 'application/json') .set('authorization', `Bearer ${token}`) @@ -32,24 +34,68 @@ const getProject = (projectId) => { `Failed to get project details of project id: ${projectId}.` + (errorDetails ? ' Server response: ' + errorDetails : '') ); - }); + }) + )) + .catch((err) => { + err.message = 'Error generating m2m token: ' + err.message; + throw err; }) +); + +/** + * Get role id + * + * @param {String} role role + * + * @return {Promise} promise resolved to role members ids list + */ +const getRoleId = (role) => { + if (rolesCache) { + const cachedRole = _.find(rolesCache, { roleName: role }); + if (cachedRole) { + return Promise.resolve(cachedRole.id); + } + } + return M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) + .then((token) => ( + request + .get(`${config.TC_API_V3_BASE_URL}/roles`) + .set('accept', 'application/json') + .set('authorization', `Bearer ${token}`) + .then((res) => { + if (!_.get(res, 'body.result.success')) { + throw new Error('Failed to get roles list'); + } + const roles = _.get(res, 'body.result.content'); + rolesCache = roles; + return _.find(roles, { roleName: role }).id; + }).catch((err) => { + const errorDetails = _.get(err, 'response.body.result.content.message'); + throw new Error( + `Failed to get role id for role ${role}.` + + (errorDetails ? ' Server response: ' + errorDetails : '') + ); + }) + )) .catch((err) => { err.message = 'Error generating m2m token: ' + err.message; throw err; }); }; + /** * Get role members * - * @param {String} roleId role id + * @param {String} role role * * @return {Promise} promise resolved to role members ids list */ -const getRoleMembers = (roleId) => { - return M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) - .then((token) => { - return request +const getRoleMembers = (role) => ( + M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) + .then((token) => ( + getRoleId(role) + .then(roleId => ( + request .get(`${config.TC_API_V3_BASE_URL}/roles/${roleId}?fields=subjects`) .set('accept', 'application/json') .set('authorization', `Bearer ${token}`) @@ -65,13 +111,14 @@ const getRoleMembers = (roleId) => { `Failed to get role membrs of role id: ${roleId}.` + (errorDetails ? ' Server response: ' + errorDetails : '') ); - }); - }) + }) + )) + )) .catch((err) => { err.message = 'Error generating m2m token: ' + err.message; throw err; - }); -}; + }) +); /** * Get users details by ids @@ -88,8 +135,9 @@ const getUsersById = (ids) => { throw err; }) .then((token) => { + const fields = 'fields=userId,email,handle,firstName,lastName,photoURL,status'; return request - .get(`${config.TC_API_V3_BASE_URL}/members/_search?fields=userId,email,handle,firstName,lastName,photoURL,status&query=${query}`) + .get(`${config.TC_API_V3_BASE_URL}/members/_search?${fields}&query=${query}`) .set('accept', 'application/json') .set('authorization', `Bearer ${token}`) .then((res) => { @@ -125,8 +173,9 @@ const getUsersByHandle = (handles) => { throw err; }) .then((token) => { + const fields = 'fields=userId,handle,firstName,lastName,photoURL'; return request - .get(`${config.TC_API_V3_BASE_URL}/members/_search?fields=userId,handle,firstName,lastName,photoURL&query=${query}`) + .get(`${config.TC_API_V3_BASE_URL}/members/_search?${fields}&query=${query}`) .set('accept', 'application/json') .set('authorization', `Bearer ${token}`) .then((res) => { @@ -154,10 +203,10 @@ const getUsersByHandle = (handles) => { * * @return {Promise} promise resolved to topic details */ -const getTopic = (topicId, logger) => { - return M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) - .then((token) => { - return request +const getTopic = (topicId, logger) => ( + M2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) + .then((token) => ( + request .get(`${config.MESSAGE_API_BASE_URL}/topics/${topicId}/read`) .set('accept', 'application/json') .set('authorization', `Bearer ${token}`) @@ -175,13 +224,13 @@ const getTopic = (topicId, logger) => { `Failed to get topic details of topic id: ${topicId}.` + (errorDetails ? ' Server response: ' + errorDetails : '') ); - }); - }) + }) + )) .catch((err) => { err.message = 'Error generating m2m token: ' + err.message; throw err; - }); -}; + }) +); module.exports = { getProject, diff --git a/docs/tc-notification-server-api.postman_collection.json b/docs/tc-notification-server-api.postman_collection.json index e3b1c4e..f7c65c0 100644 --- a/docs/tc-notification-server-api.postman_collection.json +++ b/docs/tc-notification-server-api.postman_collection.json @@ -23,7 +23,7 @@ "description": "", "auth": null, "events": null, - "collection": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "folder": null, "order": [ "1b3b6480-ea94-4027-8898-f82f28e2bea6", @@ -40,6 +40,7 @@ "requests": [ { "id": "19332a51-03e8-4f5c-8f85-4d28d6dfe6f4", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "getSettings", "url": "{{URL}}/settings", "description": "", @@ -71,6 +72,7 @@ }, { "id": "1b3b6480-ea94-4027-8898-f82f28e2bea6", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "listNotifications - invalid read filter", "url": "{{URL}}/list?offset=0&limit=20&type=notifications.connect.project.updated&read=yes", "description": "", @@ -131,6 +133,7 @@ }, { "id": "543cab06-2c7d-4aed-8cf3-0808463254d5", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "markAllRead", "url": "{{URL}}/read", "description": "", @@ -162,6 +165,7 @@ }, { "id": "59fc9f2b-28c5-4cff-b21b-11ab51bf67d8", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "getSettings - invalid token", "url": "{{URL}}/settings", "description": "", @@ -193,6 +197,7 @@ }, { "id": "76779830-a8a4-4636-8c03-1801b3d1863d", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "markAsRead", "url": "{{URL}}/1/read", "description": "", @@ -226,6 +231,7 @@ "id": "cb2299a5-dac7-4c40-80c4-7b1694138354", "name": "TC API - get project", "url": "https://api.topcoder-dev.com/v4/projects/1936", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "description": "", "data": [], "dataMode": "raw", @@ -339,7 +345,7 @@ ], "cookies": [], "request": "cb2299a5-dac7-4c40-80c4-7b1694138354", - "collection": "3f30c4e3-3b7a-491b-bdb2-6629d081a452" + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452" } ], "rawModeData": "", @@ -351,6 +357,7 @@ "name": "markAsRead - not found", "url": "{{URL}}/1111111/read", "description": "", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "data": [], "dataMode": "raw", "headerData": [ @@ -380,6 +387,7 @@ { "id": "d293d2c5-230d-4f34-8c97-1adc1f2f89b4", "name": "listNotifications - invalid limit", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "url": "{{URL}}/list?offset=0&limit=abc&type=notifications.connect.project.updated", "description": "", "data": [], @@ -441,6 +449,7 @@ "id": "d57ba947-a5e7-410a-b978-76882f33c86e", "name": "updateSettings", "url": "{{URL}}/settings", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "description": "", "data": [], "dataMode": "raw", @@ -471,6 +480,7 @@ { "id": "da23d550-55b3-4f7d-9131-735956d62f6d", "name": "markAllRead - missing token", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "url": "{{URL}}/read", "description": "", "data": [], @@ -495,6 +505,7 @@ }, { "id": "f2246cf7-7aae-4ea0-9d92-1d932d340302", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "updateSettings - invalid body", "url": "{{URL}}/settings", "description": "", @@ -527,6 +538,7 @@ { "id": "f3f3a847-46f6-4059-b167-b436078fb112", "name": "listNotifications - invalid offset", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "url": "{{URL}}/list?offset=-1&limit=20&type=notifications.connect.project.updated", "description": "", "data": [], @@ -586,6 +598,7 @@ }, { "id": "fce69847-5bf8-4b07-bcaf-6352db4ba923", + "collectionId": "3f30c4e3-3b7a-491b-bdb2-6629d081a452", "name": "listNotifications", "url": "{{URL}}/list?offset=0&limit=20", "description": "", @@ -645,4 +658,4 @@ "pathVariables": {} } ] -} \ No newline at end of file +} diff --git a/emails/src/partials/project-team.html b/emails/src/partials/project-team.html index 733ee86..89deb86 100644 --- a/emails/src/partials/project-team.html +++ b/emails/src/partials/project-team.html @@ -21,7 +21,7 @@