diff --git a/firestore.rules b/firestore.rules index 825d2ea2..b8f29261 100644 --- a/firestore.rules +++ b/firestore.rules @@ -111,7 +111,8 @@ service cloud.firestore { match /collections/{homebrewId} { allow read: if request.auth.uid != null; allow create: if request.auth.uid != null; - allow write: if request.auth.uid in resource.data.editors; + allow write: if request.auth.uid in resource.data.editors && !request.resource.data.diff(resource.data).affectedKeys().hasAny(["creator", "editors"]); + } match /stats/{statId} { @@ -179,6 +180,9 @@ service cloud.firestore { allow create: if request.auth.uid != null; allow write: if checkIsHomebrewOwner(resource.data.collectionId); } + match /editorInviteKeys/{inviteKeyId} { + allow read, write: if false; + } } match /users/{userId} { allow read: if true; diff --git a/functions/src/index.ts b/functions/src/index.ts index b21e3d78..f2ea3f6b 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -58,9 +58,13 @@ export const getHomebrewEditorInviteKey = onCall< if (linkDocuments.length === 0) { logger.info("No link documents found, creating one."); - getFirestore().collection("/homebrew/homebrew/editorInviteKeys").add({ - collectionId: homebrewCollectionId, - }); + const doc = await getFirestore() + .collection("/homebrew/homebrew/editorInviteKeys") + .add({ + collectionId: homebrewCollectionId, + }); + + return doc.id; } return linkDocuments[0].id; diff --git a/src/api-calls/homebrew/editorFunction/acceptEditorInvite.ts b/src/api-calls/homebrew/editorFunction/acceptEditorInvite.ts new file mode 100644 index 00000000..103df719 --- /dev/null +++ b/src/api-calls/homebrew/editorFunction/acceptEditorInvite.ts @@ -0,0 +1,23 @@ +import { functions } from "config/firebase.config"; +import { httpsCallable } from "firebase/functions"; + +export function acceptEditorInvite( + homebrewCollectionId: string, + inviteKey: string +): Promise { + return new Promise((resolve, reject) => { + const acceptInvite = httpsCallable( + functions, + "addCurrentUserAsHomebrewCampaignEditor" + ); + + acceptInvite({ homebrewCollectionId, inviteKey }) + .then((wasSuccessful) => { + resolve(wasSuccessful.data as boolean); + }) + .catch((e) => { + console.error(e); + reject(e); + }); + }); +} diff --git a/src/api-calls/homebrew/editorFunction/getEditorInviteUrl.ts b/src/api-calls/homebrew/editorFunction/getEditorInviteUrl.ts new file mode 100644 index 00000000..d6b7a83f --- /dev/null +++ b/src/api-calls/homebrew/editorFunction/getEditorInviteUrl.ts @@ -0,0 +1,24 @@ +import { functions } from "config/firebase.config"; +import { httpsCallable } from "firebase/functions"; + +export function getEditorInviteUrl( + homebrewCollectionId: string +): Promise { + return new Promise((resolve, reject) => { + const getInviteKey = httpsCallable(functions, "getHomebrewEditorInviteKey"); + + getInviteKey({ homebrewCollectionId }) + .then((inviteKey) => { + if (inviteKey.data) { + resolve(new URL(`/homebrew/invite/${inviteKey.data}`).toString()); + } else { + console.error("NO INVITE KEY RETURNED"); + reject(new Error("No invite key was returned")); + } + }) + .catch((e) => { + console.error(e); + reject(e); + }); + }); +} diff --git a/src/api-calls/homebrew/editorFunction/getHomebrewCollectionFromInviteUrl.ts b/src/api-calls/homebrew/editorFunction/getHomebrewCollectionFromInviteUrl.ts new file mode 100644 index 00000000..08e568b6 --- /dev/null +++ b/src/api-calls/homebrew/editorFunction/getHomebrewCollectionFromInviteUrl.ts @@ -0,0 +1,24 @@ +import { functions } from "config/firebase.config"; +import { httpsCallable } from "firebase/functions"; + +export function getHomebrewCollectionFromInviteUrl(): Promise { + return new Promise((resolve, reject) => { + const inviteKey = location.pathname.substring( + location.pathname.lastIndexOf("/") + 1 + ); + + const getHomebrewId = httpsCallable( + functions, + "getHomebrewIdFromInviteKey" + ); + + getHomebrewId({ inviteKey }) + .then((homebrewId) => { + return homebrewId.data; + }) + .catch((e) => { + console.error(e); + reject(e); + }); + }); +} diff --git a/src/config/firebase.config.ts b/src/config/firebase.config.ts index fc6545e4..b789c0f2 100644 --- a/src/config/firebase.config.ts +++ b/src/config/firebase.config.ts @@ -2,6 +2,7 @@ import { FirebaseOptions, initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; import { getStorage } from "firebase/storage"; +import { getFunctions } from "firebase/functions"; import { getSystem } from "hooks/useGameSystem"; import { GAME_SYSTEMS, GameSystemChooser } from "types/GameSystems.type"; @@ -36,3 +37,5 @@ export const firebaseAuth = getAuth(firebaseApp); export const firestore = getFirestore(firebaseApp); export const storage = getStorage(firebaseApp); + +export const functions = getFunctions(firebaseApp);