diff --git a/netlify/functions/updateWorkspaceMember.js b/netlify/functions/updateWorkspaceMember.js new file mode 100644 index 0000000..e81606a --- /dev/null +++ b/netlify/functions/updateWorkspaceMember.js @@ -0,0 +1,5 @@ +'use strict'; + +const extrovert = require('extrovert'); + +module.exports = extrovert.toNetlifyFunction(require('../../src/actions/updateWorkspaceMember')); diff --git a/src/actions/updateWorkspaceMember.js b/src/actions/updateWorkspaceMember.js new file mode 100644 index 0000000..2886782 --- /dev/null +++ b/src/actions/updateWorkspaceMember.js @@ -0,0 +1,61 @@ +'use strict'; + +const Archetype = require('archetype'); +const connect = require('../../src/db'); +const mongoose = require('mongoose'); +const stripe = require('../integrations/stripe'); + +const UpdateWorkspaceMemberParams = new Archetype({ + authorization: { + $type: 'string', + $required: true + }, + workspaceId: { + $type: mongoose.Types.ObjectId, + $required: true + }, + userId: { + $type: mongoose.Types.ObjectId, + $required: true + }, + role: { + $type: 'string', + $required: true, + $enum: ['admin', 'member', 'readonly', 'dashboards'] + } +}).compile('UpdateWorkspaceMemberParams'); + +module.exports = async function updateWorkspaceMember(params) { + const db = await connect(); + const { AccessToken, User, Workspace } = db.models; + + const { authorization, workspaceId, userId, role } = new UpdateWorkspaceMemberParams(params); + + const accessToken = await AccessToken.findById(authorization).orFail(new Error('Invalid or expired access token')); + if (accessToken.expiresAt < new Date()) { + throw new Error('Access token has expired'); + } + const initiatedByUserId = accessToken.userId; + + const workspace = await Workspace.findById(workspaceId).orFail(new Error('Workspace not found')); + const initiatedByUserRoles = workspace.members.find(member => member.userId.toString() === initiatedByUserId.toString())?.roles; + if (initiatedByUserRoles == null || (!initiatedByUserRoles.includes('admin') && !initiatedByUserRoles.includes('owner'))) { + throw new Error('Forbidden'); + } + + const member = workspace.members.find(currentMember => currentMember.userId.toString() === userId.toString()); + if (member == null) { + throw new Error('Member not found in the workspace'); + } + + member.roles = [role]; + await workspace.save(); + + const users = await User.find({ _id: { $in: workspace.members.map(currentMember => currentMember.userId) } }); + if (workspace.stripeSubscriptionId) { + const seats = users.filter(user => !user.isFreeUser).length; + await stripe.updateSubscriptionSeats(workspace.stripeSubscriptionId, seats); + } + + return { workspace, users }; +}; diff --git a/test/updateWorkspaceMember.test.js b/test/updateWorkspaceMember.test.js new file mode 100644 index 0000000..6cdd19f --- /dev/null +++ b/test/updateWorkspaceMember.test.js @@ -0,0 +1,75 @@ +'use strict'; + +const { afterEach, beforeEach, describe, it } = require('mocha'); +const assert = require('assert'); +const connect = require('../src/db'); +const updateWorkspaceMember = require('../src/actions/updateWorkspaceMember'); + +describe('updateWorkspaceMember', function() { + let db, AccessToken, User, Workspace; + let user, workspace, accessToken, memberUser; + + beforeEach(async function() { + db = await connect(); + ({ AccessToken, User, Workspace } = db.models); + + await AccessToken.deleteMany({}); + await User.deleteMany({}); + await Workspace.deleteMany({}); + + user = await User.create({ + name: 'John Doe', + email: 'johndoe@example.com', + githubUsername: 'johndoe', + githubUserId: '1234' + }); + + memberUser = await User.create({ + name: 'Jane Smith', + email: 'janesmith@example.com', + githubUsername: 'janesmith', + githubUserId: '5678' + }); + + accessToken = await AccessToken.create({ + userId: user._id, + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) + }); + + workspace = await Workspace.create({ + name: 'Test Workspace', + ownerId: user._id, + apiKey: 'test-api-key', + baseUrl: 'https://example.com', + members: [ + { userId: user._id, roles: ['owner'] }, + { userId: memberUser._id, roles: ['member'] } + ], + subscriptionTier: 'pro' + }); + }); + + afterEach(async function() { + await AccessToken.deleteMany({}); + await User.deleteMany({}); + await Workspace.deleteMany({}); + }); + + it('updates the role of a workspace member', async function() { + const result = await updateWorkspaceMember({ + authorization: accessToken._id.toString(), + workspaceId: workspace._id, + userId: memberUser._id, + role: 'admin' + }); + + assert.ok(result.workspace); + + const updatedMember = result.workspace.members.find(member => member.userId.toString() === memberUser._id.toString()); + assert.deepStrictEqual(updatedMember.roles, ['admin']); + + const workspaceInDb = await Workspace.findById(workspace._id); + const updatedMemberInDb = workspaceInDb.members.find(member => member.userId.toString() === memberUser._id.toString()); + assert.deepStrictEqual(updatedMemberInDb.roles, ['admin']); + }); +});