-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial support for durable invitation links.
- Add invitation codes to the hunt schema. Codes are randomly generated IDs. - Allow admins and hunt operators to (re)generate and clear invitation links from the hunt profile list page. - Allow any user with invitation permissions to see the current invitation link (if any) from the hunt profile list page. - Add an authenticated /join/:invitationCode endpoint which adds the current user to the hunt with that code and redirects to that hunt page. (If a user is unauthenticated when they open this link, they will be redirected here after signing in.) See #2047
- Loading branch information
Showing
14 changed files
with
278 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { useNavigate, useParams } from "react-router-dom"; | ||
import acceptHuntInvitationCode from "../../methods/acceptHuntInvitationCode"; | ||
|
||
const JoinHunt = () => { | ||
const invitationCode = useParams<"invitationCode">().invitationCode!; | ||
const [status, setStatus] = useState<string>("loading..."); | ||
|
||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
acceptHuntInvitationCode.call({ invitationCode }, (error, huntId) => { | ||
if (error) { | ||
setStatus(error.reason ?? "Unknown error"); | ||
} else { | ||
navigate(`/hunts/${huntId}`); | ||
} | ||
}); | ||
}, [invitationCode, navigate]); | ||
|
||
return <div>{status}</div>; | ||
}; | ||
|
||
export default JoinHunt; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import TypedMethod from "./TypedMethod"; | ||
|
||
export default new TypedMethod<{ invitationCode: string }, string>( | ||
"Hunts.methods.acceptHuntInvitationCode", | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import TypedMethod from "./TypedMethod"; | ||
|
||
export default new TypedMethod<{ huntId: string }, void>( | ||
"Hunts.methods.clearHuntInvitationCode", | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import TypedMethod from "./TypedMethod"; | ||
|
||
export default new TypedMethod<{ huntId: string }, string>( | ||
"Hunts.methods.generateHuntInvitationCode", | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { check } from "meteor/check"; | ||
import { Meteor } from "meteor/meteor"; | ||
import Hunts from "../../lib/models/Hunts"; | ||
import MeteorUsers from "../../lib/models/MeteorUsers"; | ||
import acceptHuntInvitationCode from "../../methods/acceptHuntInvitationCode"; | ||
import addUserToHunt from "../addUserToHunt"; | ||
import defineMethod from "./defineMethod"; | ||
|
||
defineMethod(acceptHuntInvitationCode, { | ||
validate(arg) { | ||
check(arg, { | ||
invitationCode: String, | ||
}); | ||
return arg; | ||
}, | ||
|
||
async run({ invitationCode }): Promise<string> { | ||
check(this.userId, String); | ||
|
||
const hunt = await Hunts.findOneAsync({ | ||
invitationCode, | ||
}); | ||
if (!hunt) { | ||
throw new Meteor.Error(404, "Invalid invitation code"); | ||
} | ||
|
||
const user = await MeteorUsers.findOneAsync(this.userId); | ||
const email = user?.emails?.[0]?.address; | ||
if (!email) { | ||
throw new Meteor.Error(500, "No email found for current user"); | ||
} | ||
|
||
await addUserToHunt({ hunt, email, invitedBy: this.userId }); | ||
|
||
return hunt._id; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { check } from "meteor/check"; | ||
import { Meteor } from "meteor/meteor"; | ||
import Hunts from "../../lib/models/Hunts"; | ||
import MeteorUsers from "../../lib/models/MeteorUsers"; | ||
import { userMayUpdateHuntInvitationCode } from "../../lib/permission_stubs"; | ||
import clearHuntInvitationCode from "../../methods/clearHuntInvitationCode"; | ||
import defineMethod from "./defineMethod"; | ||
|
||
// Clear the invitation code for the given hunt. | ||
defineMethod(clearHuntInvitationCode, { | ||
validate(arg) { | ||
check(arg, { | ||
huntId: String, | ||
}); | ||
return arg; | ||
}, | ||
|
||
async run({ huntId }) { | ||
check(this.userId, String); | ||
|
||
const hunt = await Hunts.findOneAsync(huntId); | ||
if (!hunt) { | ||
throw new Meteor.Error(404, "Unknown hunt"); | ||
} | ||
|
||
const user = await MeteorUsers.findOneAsync(this.userId); | ||
|
||
if (!userMayUpdateHuntInvitationCode(user, hunt)) { | ||
throw new Meteor.Error( | ||
401, | ||
`User ${this.userId} may not clear invitation code for ${huntId}`, | ||
); | ||
} | ||
|
||
await Hunts.updateAsync( | ||
{ _id: huntId }, | ||
{ | ||
$unset: { | ||
invitationCode: 1, | ||
}, | ||
}, | ||
); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { check } from "meteor/check"; | ||
import { Meteor } from "meteor/meteor"; | ||
import { Random } from "meteor/random"; | ||
import Hunts from "../../lib/models/Hunts"; | ||
import MeteorUsers from "../../lib/models/MeteorUsers"; | ||
import { userMayUpdateHuntInvitationCode } from "../../lib/permission_stubs"; | ||
import generateHuntInvitationCode from "../../methods/generateHuntInvitationCode"; | ||
import defineMethod from "./defineMethod"; | ||
|
||
// Generate (or regenerate) an invitation code for the given hunt. | ||
defineMethod(generateHuntInvitationCode, { | ||
validate(arg) { | ||
check(arg, { | ||
huntId: String, | ||
}); | ||
return arg; | ||
}, | ||
|
||
async run({ huntId }) { | ||
check(this.userId, String); | ||
|
||
const hunt = await Hunts.findOneAsync(huntId); | ||
if (!hunt) { | ||
throw new Meteor.Error(404, "Unknown hunt"); | ||
} | ||
|
||
const user = await MeteorUsers.findOneAsync(this.userId); | ||
|
||
if (!userMayUpdateHuntInvitationCode(user, hunt)) { | ||
throw new Meteor.Error( | ||
401, | ||
`User ${this.userId} may not generate invitation codes for ${huntId}`, | ||
); | ||
} | ||
|
||
const newInvitationCode = Random.id(); | ||
|
||
await Hunts.updateAsync( | ||
{ _id: huntId }, | ||
{ | ||
$set: { | ||
invitationCode: newInvitationCode, | ||
}, | ||
}, | ||
); | ||
|
||
return newInvitationCode; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.