diff --git a/api/migrations/20190501021006-remove-all-private-channel-members.js b/api/migrations/20190501021006-remove-all-private-channel-members.js new file mode 100644 index 0000000000..36766c1aa7 --- /dev/null +++ b/api/migrations/20190501021006-remove-all-private-channel-members.js @@ -0,0 +1,75 @@ +exports.up = async function(r, conn) { + // get all private channels that haven't been deleted + const privateChannels = await r + .db('spectrum') + .table('channels') + .filter({ isPrivate: true }) + .filter(row => row.hasFields('deletedAt').not()) + .run(conn) + .then(cursor => cursor.toArray()); + + // for each channel, remove all members except the community owner + return Promise.all( + privateChannels.map(async channel => { + const community = await r + .db('spectrum') + .table('communities') + .get(channel.communityId) + .run(conn); + + // ensure that the community owner also owns the channel + // to account for situations where a moderator created the channel + const communityOwnerChannelRecord = await r + .db('spectrum') + .table('usersChannels') + .getAll([community.creatorId, channel.id], { + index: 'userIdAndChannelId', + }) + .run(conn) + .then(cursor => cursor.toArray()); + + if ( + !communityOwnerChannelRecord || + communityOwnerChannelRecord.length === 0 + ) { + await r + .db('spectrum') + .table('usersChannels') + .insert({ + channelId: channel.id, + userId: community.creatorId, + createdAt: new Date(), + isOwner: true, + isMember: true, + isModerator: false, + isBlocked: false, + isPending: false, + receiveNotifications: false, + }) + .run(conn); + } else { + await r + .db('spectrum') + .table('usersChannels') + .getAll([community.creatorId, channel.id], { + index: 'userIdAndChannelId', + }) + .update({ isOwner: true }) + .run(conn); + } + + return await r + .db('spectrum') + .table('usersChannels') + .getAll(channel.id, { index: 'channelId' }) + .filter({ isMember: true }) + .filter(row => row('userId').ne(community.creatorId)) + .update({ isMember: false }) + .run(conn); + }) + ); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/models/usersChannels.js b/api/models/usersChannels.js index e6bfea7921..ff7b4ed786 100644 --- a/api/models/usersChannels.js +++ b/api/models/usersChannels.js @@ -466,6 +466,11 @@ const toggleUserChannelNotifications = async (userId: string, channelId: string, .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .run(); + const channel = await db + .table('channels') + .get(channelId) + .run() + // permissions exist, this user is trying to toggle notifications for a channel where they // are already a member if (permissions && permissions.length > 0) { @@ -476,8 +481,16 @@ const toggleUserChannelNotifications = async (userId: string, channelId: string, .run(); } - // if permissions don't exist, create a usersChannel relationship with notifications on - return createMemberInChannel(channelId, userId, false) + // if the channel isn't private, it means the user is enabling notifications + // in a public channel that they have not yet joined - for example, if a user + // joins a community, then some time later the community creates a new channel, + // then again some time later the user wants notifications about that channel + if (!channel.isPrivate) { + // if permissions don't exist, create a usersChannel relationship with notifications on + return createMemberInChannel(channelId, userId, false) + } + + return }; const removeUsersChannelMemberships = async (userId: string) => { diff --git a/api/package.json b/api/package.json index 90df78ffed..9d70987a45 100644 --- a/api/package.json +++ b/api/package.json @@ -81,7 +81,7 @@ "ms": "^2.1.1", "node-env-file": "^0.1.8", "node-localstorage": "^1.3.1", - "now-env": "^3.1.0", + "now-env": "^3.2.0", "offline-plugin": "^4.9.1", "passport": "^0.3.2", "passport-facebook": "^2.1.1", diff --git a/api/routes/api/email.js b/api/routes/api/email.js index d6f6110b9a..ab4b36b44d 100644 --- a/api/routes/api/email.js +++ b/api/routes/api/email.js @@ -1,8 +1,6 @@ // @flow require('now-env'); const IS_PROD = process.env.NODE_ENV === 'production'; -const IS_TESTING = process.env.TEST_DB; -import { BRIAN_ID } from '../../migrations/seed/default/constants'; import { Router } from 'express'; const jwt = require('jsonwebtoken'); const emailRouter = Router(); @@ -10,10 +8,12 @@ import { updateUserEmail } from 'shared/db/queries/user'; import { unsubscribeUserFromEmailNotification } from '../../models/usersSettings'; import { updateThreadNotificationStatusForUser } from '../../models/usersThreads'; import { updateDirectMessageThreadNotificationStatusForUser } from '../../models/usersDirectMessageThreads'; -import { toggleUserChannelNotifications } from '../../models/usersChannels'; +import { + toggleUserChannelNotifications, + getUsersPermissionsInChannels, +} from '../../models/usersChannels'; import { updateCommunityAdministratorEmail, - resetCommunityAdministratorEmail, getCommunityById, } from '../../models/community'; import { getChannelsByCommunity, getChannelById } from '../../models/channel'; @@ -89,18 +89,28 @@ emailRouter.get('/unsubscribe', async (req, res) => { } case 'muteCommunity': { const community = await getCommunityById(dataId); - return getChannelsByCommunity(dataId) - .then(channels => channels.map(c => c.id)) - .then(channels => - channels.map(c => toggleUserChannelNotifications(userId, c, false)) - ) - .then(() => - res.redirect( - `${rootRedirect}/${ - community.slug - }?toastType=success&toastMessage=You will no longer receive new thread emails from this community.` - ) - ); + const channels = await getChannelsByCommunity(dataId); + const channelIds = channels.map(channel => channel.id); + const usersChannels = await getUsersPermissionsInChannels( + channelIds.map(id => [userId, id]) + ); + const usersChannelsWithNotifications = usersChannels.filter( + usersChannel => usersChannel && usersChannel.receiveNotifications + ); + const channelIdsWithNotifications = usersChannelsWithNotifications.map( + usersChannel => usersChannel.channelId + ); + + await channelIdsWithNotifications.map( + async channelId => + await toggleUserChannelNotifications(userId, channelId, false) + ); + + return res.redirect( + `${rootRedirect}/${ + community.slug + }?toastType=success&toastMessage=You will no longer receive new thread emails from this community.` + ); } case 'muteThread': return updateThreadNotificationStatusForUser( diff --git a/api/yarn.lock b/api/yarn.lock index dd1abf5ac7..dad95a5599 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -7100,10 +7100,10 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -now-env@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" - integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== +now-env@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.2.0.tgz#33223dd000d7966e66c0d00779c28c7aa0f111c6" + integrity sha512-zCWAwdX1KTa2ZoEg6Rc8efym5V4I36sC52OXo9F3O4IaNrvd/o1z7ZjuwnevoQqLloIdQWHo3JYAcL12m+KsUQ== npm-bundled@^1.0.1: version "1.0.5" diff --git a/package.json b/package.json index 9273d55814..1b88f4ff83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Spectrum", - "version": "3.1.10", + "version": "3.1.11", "license": "BSD-3-Clause", "devDependencies": { "@babel/preset-flow": "^7.0.0", diff --git a/vulcan/package.json b/vulcan/package.json index 4cd093cd77..9db1da4a23 100644 --- a/vulcan/package.json +++ b/vulcan/package.json @@ -15,7 +15,7 @@ "lodash": "^4.17.11", "lodash.intersection": "^4.4.0", "node-env-file": "^0.1.8", - "now-env": "^3.1.0", + "now-env": "^3.2.0", "performance-now": "^2.1.0", "raven": "^2.6.4", "redis-tag-cache": "^1.2.1", diff --git a/vulcan/yarn.lock b/vulcan/yarn.lock index 4af431ed94..dd5d88fd62 100644 --- a/vulcan/yarn.lock +++ b/vulcan/yarn.lock @@ -667,10 +667,10 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -now-env@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" - integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== +now-env@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.2.0.tgz#33223dd000d7966e66c0d00779c28c7aa0f111c6" + integrity sha512-zCWAwdX1KTa2ZoEg6Rc8efym5V4I36sC52OXo9F3O4IaNrvd/o1z7ZjuwnevoQqLloIdQWHo3JYAcL12m+KsUQ== object-assign@^4.1.0: version "4.1.1"