Skip to content

Commit 33fde1e

Browse files
authored
Decline Project Invite - Service (#264)
* Add the decline route and route logic * Unauthenticated /decline, Authenticated and Permission Checked /remove * phew a lot more to it than anticipated * changes from testing and thinking * All the way through with the right UX * All the way through with the right UX * All the way through with the right UX * cleanup * cleanup * cleanup
1 parent d6e4ac7 commit 33fde1e

4 files changed

Lines changed: 91 additions & 11 deletions

File tree

classes/Project/Project.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,16 @@ export default class Project {
6565
let message = `You have been invited to the TPEN project ${projectTitle}.
6666
View project <a href='${process.env.TPENINTERFACES}project?projectID=${this.data._id}'>here</a>.`
6767
if (user) {
68+
// FIXME this does not have the functionality of an 'invite'.
6869
await this.inviteExistingTPENUser(user._id, roles)
6970
}
7071
else {
7172
const inviteData = await this.inviteNewTPENUser(email, roles)
7273
const returnTo = encodeURIComponent(`${process.env.TPENINTERFACES}project?projectID=${this.data._id}&inviteCode=${inviteData.tpenUserID}`)
7374
// Signup starting at the TPEN3 public site
7475
const signup = `${process.env.TPENTHREE}login?inviteCode=${inviteData.tpenUserID}&returnTo=${returnTo}`
75-
// TODO decline endpoint in TPEN Services
76-
const decline = `${process.env.TPENINTERFACES}project/decline?inviteCode=${inviteData.tpenUserID}&groupID=${inviteData.tpenGroupID}`
76+
// Decline endpoint in TPEN Services
77+
const decline = `${process.env.TPENINTERFACES}project/decline?email=${encodeURIComponent(email)}&user=${inviteData.tpenUserID}&project=${this.data._id}&projectTitle=${encodeURIComponent(projectTitle)}`
7778
message += `
7879
<p>
7980
Click the button below to get started with your project</p>
@@ -138,12 +139,19 @@ export default class Project {
138139
return roles
139140
}
140141

142+
/**
143+
* Invite an existing TPEN3 User to the project.
144+
* FIXME this does not have the functionality of an 'invite'. The User is added to the project.
145+
* There is no step for them to accept or decline.
146+
*/
141147
async inviteExistingTPENUser(userId, roles) {
142-
const group = new Group(this.data.group)
143-
await group.addMember(userId, roles)
148+
await this.addMember(userId, roles)
144149
return this
145150
}
146151

152+
/**
153+
* Add a new temporary user to the users collection and send the invite E-mail.
154+
*/
147155
async inviteNewTPENUser(email, roles) {
148156
const user = new User()
149157
const inviteCode = user._id
@@ -152,15 +160,44 @@ export default class Project {
152160
const _sub = `temp-${user._id}` // This is a temporary sub for the user until they verify their email
153161
user.data = { email, _sub, profile, agent, inviteCode }
154162
await user.save()
163+
// FIXME this does not have the functionality of an 'invite'.
155164
await this.inviteExistingTPENUser(user._id, roles)
156165
return { "tpenUserID":user._id, "tpenGroupID":this.data.group }
157166
}
158167

168+
/**
169+
* Add a member to the Project Group.
170+
*
171+
* @param userId The User/member _id to add to the Group.
172+
*/
173+
async addMember(userId, roles) {
174+
try {
175+
const group = new Group(this.data.group)
176+
await group.addMember(userId, roles)
177+
} catch (error) {
178+
throw {
179+
status: error.status || 500,
180+
message: error.message || "An error occurred while adding the member."
181+
}
182+
}
183+
184+
}
185+
186+
/**
187+
* Remove a member from the Project Group.
188+
* If the member is an invitee (temporary) User, delete that User from the db.
189+
*
190+
* @param userId The User/member _id to remove from the Group and perhaps delete from the db.
191+
*/
159192
async removeMember(userId) {
160193
try {
161194
const group = new Group(this.data.group)
162195
await group.removeMember(userId)
163196
await group.update()
197+
// Don't leave orphaned invitees in the db.
198+
const member = new User(userId)
199+
const memberData = await member.getSelf()
200+
if(memberData?.inviteCode) member.delete()
164201
return this
165202
} catch (error) {
166203
throw {

project/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import hotkeysRouter from "./hotkeysRouter.js"
1111
import metadataRouter from "./metadataRouter.js"
1212
import projectToolsRouter from "./projectToolsRouter.js"
1313
import memberUpgradeRouter from "./memberUpgradeRouter.js"
14+
import memberDeclineInviteRouter from "./memberDeclineInviteRouter.js"
1415

1516
const router = express.Router({ mergeParams: true })
1617
router.use(cors(common_cors))
1718

1819
// Use split routers
1920
router.use(memberUpgradeRouter) // Contains unauthenticated route!
21+
router.use(memberDeclineInviteRouter) // Contains unauthenticated route!
2022
router.use(projectCreateRouter)
2123
router.use(import28Router)
2224
router.use(projectReadRouter)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import express from "express"
2+
import { validateID, respondWithError } from "../utilities/shared.js"
3+
import Project from "../classes/Project/Project.js"
4+
import User from "../classes/User/User.js"
5+
6+
const router = express.Router({ mergeParams: true })
7+
8+
/**
9+
* A user is declining from the E-mail they recieved. It is unauthenticated.
10+
* Their member entry should be removed from the Group.
11+
* Their temporary user should be removed from the db.
12+
*
13+
* @param projectId - The project which contains the temporary TPEN3 User as a member.
14+
* @param collaboratorId - The temporary TPEN3 User declining the invitation.
15+
*/
16+
router.route("/:projectId/collaborator/:collaboratorId/decline").get(async (req, res) => {
17+
const { projectId, collaboratorId } = req.params
18+
if (!projectId || !collaboratorId) return respondWithError(res, 400, "Not all data was provided.")
19+
try {
20+
const project = await new Project(projectId)
21+
const projectData = await project.loadProject()
22+
if (!projectData) return respondWithError(res, 404, "Project does not exist or the project id is invalid.")
23+
const invitedUser = new User(collaboratorId)
24+
const userData = await invitedUser.getSelf()
25+
if (!userData?.profile) return respondWithError(res, 404, "This user has already declined or the user id is invalid.")
26+
if (!userData?.inviteCode) return respondWithError(res, 400, "This user has already accepted the invitation.")
27+
await project.removeMember(collaboratorId)
28+
const name = userData.email ?? userData.profile.displayName ?? collaboratorId
29+
res.status(200).send(`User '${name}' successfully declined the invitation.`)
30+
} catch (error) {
31+
return respondWithError(
32+
res,
33+
error.status || error.code || 500,
34+
error.message ?? "There was an error declining the invitation."
35+
)
36+
}
37+
}).all((_, res) => {
38+
respondWithError(res, 405, "Improper request method. Use GET instead")
39+
})
40+
41+
export default router

project/memberUpgradeRouter.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ router.route("/:projectId/collaborator/:collaboratorId/agent/:agentId").get(asyn
2828
const tempData = await tempUser.getSelf()
2929
if(!tempData?.profile) return respondWithError(res, 404, "Temporary user does not exist")
3030
if(!tempData?.inviteCode) return respondWithError(res, 400, "Temporary user provided is not a temporary user.")
31-
const project = await new Project(projectId).loadProject()
32-
if(!project) return respondWithError(res, 404, "Project does not exist.")
33-
const group = new Group(project.group)
31+
const project = new Project(projectId)
32+
const projectData = await project.loadProject()
33+
if(!projectData) return respondWithError(res, 404, "Project does not exist.")
34+
const group = new Group(projectData.group)
3435
let tempRoles = await group.getMemberRoles(tempData._id)
3536
if(!tempRoles) tempRoles = {"VIEWER":[]}
36-
group.addMember(agentId, Object.keys(tempRoles))
37+
await project.addMember(agentId, Object.keys(tempRoles))
3738
try {
38-
group.removeMember(tempData._id)
39+
// This will also delete the temporary User from the users collection.
40+
await project.removeMember(tempData._id)
3941
}
4042
catch (err) {
4143
// keep going.
4244
}
43-
await group.update()
44-
tempUser.delete()
4545
res.status(200).send(`Temporary user '${collaboratorId}' upgraded to user '${agentId}'.`)
4646
} catch (error) {
4747
return respondWithError(

0 commit comments

Comments
 (0)