Backend Documentation
This document describes the backend logic and Firebase Cloud Functions used in the StoryQuest application. These functions are responsible for managing game rooms, players, and the game lifecycle using Firestore.
Firebase Initialization
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: "storyquest-fcdc2",
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
export { db };
Environment variables should be stored in a .env.local file. Keys prefixed with NEXT_PUBLIC_ are exposed to the client.
Cloud Functions Overview
All functions are defined using Firebase's onCall HTTP trigger. They use the Firebase Admin SDK to securely interact with Firestore.
Common Setup
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
Function: createRoom
Purpose: Initializes a new game room.
Trigger Type: Callable (functions.https.onCall)
Input: None
Output: { roomId: string }
Logic:
- Generates a unique document in the
roomscollection. - Assigns the current authenticated user as the host.
- Initializes game state and player placeholders.
exports.createRoom = functions.https.onCall(async (data, context) => {
const newRoomRef = db.collection("rooms").doc();
const roomId = newRoomRef.id;
await newRoomRef.set({
gameState: "waiting",
players: {},
host: context.auth?.uid || "anonymous",
storyProgress: {}
});
return { roomId };
});
Function: joinRoom
Purpose: Allows a player to join an existing game room.
Trigger Type: Callable (functions.https.onCall)
Input: { roomId: string, playerName: string }
Output: { success: true, playerId: string }
Logic:
- Verifies the room exists.
- Adds a player to the
playersfield using either their UID or a generated guest ID.
exports.joinRoom = functions.https.onCall(async (data, context) => {
const { roomId, playerName } = data;
if (!roomId || !playerName) throw new functions.https.HttpsError("invalid-argument", "Missing fields.");
const roomRef = db.collection("rooms").doc(roomId);
const roomDoc = await roomRef.get();
if (!roomDoc.exists) throw new functions.https.HttpsError("not-found", "Room does not exist.");
const playerId = context.auth?.uid || `guest_${Math.random().toString(36).substring(2, 8)}`;
await roomRef.update({
[`players.${playerId}`]: { name: playerName, role: "player" }
});
return { success: true, playerId };
});
Function: startGame
Purpose: Updates the gameState of a room to "started".
Trigger Type: Callable (functions.https.onCall)
Input: { roomId: string }
Output: { success: true }
Logic:
- Validates that the room exists.
- Updates the
gameStatefield to"started".
exports.startGame = functions.https.onCall(async (data, context) => {
const { roomId } = data;
if (!roomId) throw new functions.https.HttpsError("invalid-argument", "Room ID is required.");
const roomRef = db.collection("rooms").doc(roomId);
const roomDoc = await roomRef.get();
if (!roomDoc.exists) throw new functions.https.HttpsError("not-found", "Room does not exist.");
await roomRef.update({ gameState: "started" });
return { success: true };
});