Skip to main content

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 rooms collection.
  • 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 players field 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 gameState field 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 };
});