M2: refactor all firebase write sites to storage adapter
- 37 call sites: setDoc/updateDoc/deleteDoc/addDoc/getDocs/writeBatch -> storage.* - adapter wraps SDK, path-string interface - storage instance app-wide (getStorage) - firebase.js: static imports (getDoc/getDocs alias), no dynamic import 56 frontend tests green. STORAGE=firebase = identical behavior.
This commit is contained in:
+53
-50
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { initializeApp } from './storage';
|
||||
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from './storage';
|
||||
import { getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage } from './storage';
|
||||
import { getFirestore, doc, setDoc, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage } from './storage';
|
||||
import {
|
||||
PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown,
|
||||
UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
|
||||
@@ -95,6 +95,7 @@ const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
|
||||
let app;
|
||||
let db;
|
||||
let auth;
|
||||
let storage;
|
||||
|
||||
// Initialize Firebase
|
||||
const initializeFirebase = () => {
|
||||
@@ -110,6 +111,7 @@ const initializeFirebase = () => {
|
||||
app = initializeApp(firebaseConfig);
|
||||
db = getFirestore(app);
|
||||
auth = getAuth(app);
|
||||
storage = getStorage();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error initializing Firebase:", error);
|
||||
@@ -162,7 +164,7 @@ const logAction = async (message, context = {}, undoData = null) => {
|
||||
try {
|
||||
const entry = { timestamp: Date.now(), message, ...context };
|
||||
if (undoData) entry.undo = undoData;
|
||||
await addDoc(collection(db, getPath.logs()), entry);
|
||||
await storage.addDoc(getPath.logs(), entry);
|
||||
} catch (err) {
|
||||
console.error('Error writing log:', err);
|
||||
}
|
||||
@@ -565,7 +567,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
||||
};
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, getPath.campaign(campaignId)), {
|
||||
await storage.updateDoc(getPath.campaign(campaignId), {
|
||||
players: [...campaignCharacters, newCharacter]
|
||||
});
|
||||
setCharacterName('');
|
||||
@@ -601,7 +603,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
||||
);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
|
||||
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
|
||||
setEditingCharacter(null);
|
||||
} catch (err) {
|
||||
console.error("Error updating character:", err);
|
||||
@@ -620,7 +622,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
||||
const updatedCharacters = campaignCharacters.filter(c => c.id !== itemToDelete.id);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
|
||||
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
|
||||
} catch (err) {
|
||||
console.error("Error deleting character:", err);
|
||||
alert("Failed to delete character. Please try again.");
|
||||
@@ -876,7 +878,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
};
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
participants: [...participants, newParticipant]
|
||||
});
|
||||
logAction(`${nameToAdd} added to encounter (Initiative: ${finalInitiative})`, { encounterName: encounter.name }, {
|
||||
@@ -941,7 +943,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
}
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
participants: [...participants, ...newParticipants]
|
||||
});
|
||||
console.log(`Added ${newParticipants.length} characters to the encounter.`);
|
||||
@@ -959,7 +961,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||
setEditingParticipant(null);
|
||||
} catch (err) {
|
||||
console.error("Error updating participant:", err);
|
||||
@@ -988,7 +990,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
};
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
participants: updatedParticipants,
|
||||
...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants)
|
||||
});
|
||||
@@ -1018,7 +1020,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
: computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
|
||||
logAction(`${participant.name} ${newIsActive ? 'reactivated' : 'deactivated'}`, { encounterName: encounter.name }, {
|
||||
encounterPath,
|
||||
updates: {
|
||||
@@ -1102,7 +1104,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
};
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
|
||||
setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
|
||||
const hpLine = `${participant.currentHp} → ${newHp} HP`;
|
||||
const deathSuffix = (isDead && !wasDead) ? (participant.type === 'character' ? ' — Unconscious' : ' — Defeated') : '';
|
||||
@@ -1133,13 +1135,13 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||
|
||||
// Wait for animation to complete on player display (2 seconds) then remove participant
|
||||
setTimeout(async () => {
|
||||
const finalParticipants = participants.filter(p => p.id !== participantId);
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: finalParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: finalParticipants });
|
||||
} catch (err) {
|
||||
console.error("Error removing dead participant:", err);
|
||||
}
|
||||
@@ -1154,7 +1156,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||
} catch (err) {
|
||||
console.error("Error updating death saves:", err);
|
||||
}
|
||||
@@ -1175,7 +1177,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
return { ...p, conditions: next };
|
||||
});
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||
const cond = CONDITIONS.find(c => c.id === conditionId);
|
||||
const condLabel = cond ? `${cond.label} ${cond.emoji}` : conditionId;
|
||||
logAction(`${participant.name} ${wasActive ? 'lost' : 'gained'} ${condLabel}`, { encounterName: encounter.name }, {
|
||||
@@ -1228,7 +1230,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
currentParticipants.splice(targetIndex, 0, removedItem);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: currentParticipants });
|
||||
await storage.updateDoc(encounterPath, { participants: currentParticipants });
|
||||
} catch (err) {
|
||||
console.error("Error reordering participants:", err);
|
||||
}
|
||||
@@ -1600,7 +1602,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
const handleToggleHidePlayerHp = async () => {
|
||||
if (!db) return;
|
||||
try {
|
||||
await setDoc(doc(db, getPath.activeDisplay()), { hidePlayerHp: !hidePlayerHp }, { merge: true });
|
||||
await storage.setDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp }, { merge: true });
|
||||
} catch (err) {
|
||||
console.error("Error toggling hidePlayerHp:", err);
|
||||
}
|
||||
@@ -1621,7 +1623,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
const sortedParticipants = sortParticipantsByInitiative(activeParticipants, encounter.participants);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
isStarted: true,
|
||||
isPaused: false,
|
||||
round: 1,
|
||||
@@ -1629,7 +1631,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
turnOrderIds: sortedParticipants.map(p => p.id)
|
||||
});
|
||||
|
||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
||||
await storage.setDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: campaignId,
|
||||
activeEncounterId: encounter.id
|
||||
}, { merge: true });
|
||||
@@ -1664,7 +1666,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
}
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
isPaused: newPausedState,
|
||||
turnOrderIds: newTurnOrderIds
|
||||
});
|
||||
@@ -1690,7 +1692,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
|
||||
if (activePsInOrder.length === 0) {
|
||||
alert("No active participants left.");
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
isStarted: false,
|
||||
isPaused: false,
|
||||
currentTurnParticipantId: null,
|
||||
@@ -1730,7 +1732,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
if (!nextParticipant) return;
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
currentTurnParticipantId: nextParticipant.id,
|
||||
round: nextRound,
|
||||
turnOrderIds: newTurnOrderIds,
|
||||
@@ -1752,7 +1754,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
if (!db) return;
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
await storage.updateDoc(encounterPath, {
|
||||
isStarted: false,
|
||||
isPaused: false,
|
||||
currentTurnParticipantId: null,
|
||||
@@ -1760,7 +1762,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
turnOrderIds: []
|
||||
});
|
||||
|
||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
||||
await storage.setDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: null,
|
||||
activeEncounterId: null
|
||||
}, { merge: true });
|
||||
@@ -1920,7 +1922,7 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
||||
const newEncounterId = generateId();
|
||||
|
||||
try {
|
||||
await setDoc(doc(db, getPath.encounters(campaignId), newEncounterId), {
|
||||
await storage.setDoc(`${getPath.encounters(campaignId)}/${newEncounterId}`, {
|
||||
name: name.trim(),
|
||||
createdAt: new Date().toISOString(),
|
||||
participants: [],
|
||||
@@ -1949,14 +1951,14 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
||||
const encounterId = itemToDelete.id;
|
||||
|
||||
try {
|
||||
await deleteDoc(doc(db, getPath.encounter(campaignId, encounterId)));
|
||||
await storage.deleteDoc(getPath.encounter(campaignId, encounterId));
|
||||
|
||||
if (selectedEncounterId === encounterId) {
|
||||
setSelectedEncounterId(null);
|
||||
}
|
||||
|
||||
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
|
||||
await updateDoc(doc(db, getPath.activeDisplay()), {
|
||||
await storage.updateDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: null,
|
||||
activeEncounterId: null
|
||||
});
|
||||
@@ -1978,12 +1980,12 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
||||
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
|
||||
|
||||
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
|
||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
||||
await storage.setDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: null,
|
||||
activeEncounterId: null,
|
||||
}, { merge: true });
|
||||
} else {
|
||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
||||
await storage.setDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: campaignId,
|
||||
activeEncounterId: encounterId,
|
||||
}, { merge: true });
|
||||
@@ -2139,8 +2141,8 @@ function AdminView({ userId }) {
|
||||
let encounterCount = 0;
|
||||
|
||||
try {
|
||||
const encountersSnapshot = await getDocs(collection(db, getPath.encounters(campaign.id)));
|
||||
encounterCount = encountersSnapshot.size;
|
||||
const encounters = await storage.getCollection(getPath.encounters(campaign.id));
|
||||
encounterCount = encounters.length;
|
||||
} catch (err) {
|
||||
console.error(`Failed to fetch encounters for campaign ${campaign.id}:`, err);
|
||||
}
|
||||
@@ -2180,7 +2182,7 @@ function AdminView({ userId }) {
|
||||
const newCampaignId = generateId();
|
||||
|
||||
try {
|
||||
await setDoc(doc(db, getPath.campaign(newCampaignId)), {
|
||||
await storage.setDoc(getPath.campaign(newCampaignId), {
|
||||
name: name.trim(),
|
||||
playerDisplayBackgroundUrl: backgroundUrl.trim() || '',
|
||||
ownerId: userId,
|
||||
@@ -2208,23 +2210,23 @@ function AdminView({ userId }) {
|
||||
|
||||
try {
|
||||
const encountersPath = getPath.encounters(campaignId);
|
||||
const encountersSnapshot = await getDocs(collection(db, encountersPath));
|
||||
const batch = writeBatch(db);
|
||||
const encounters = await storage.getCollection(encountersPath);
|
||||
const deleteOps = encounters.map(e => {
|
||||
const id = e.id || e.path?.split('/').pop();
|
||||
return { type: 'delete', path: `${encountersPath}/${id}` };
|
||||
});
|
||||
if (deleteOps.length > 0) await storage.batchWrite(deleteOps);
|
||||
|
||||
encountersSnapshot.docs.forEach(encounterDoc => batch.delete(encounterDoc.ref));
|
||||
await batch.commit();
|
||||
|
||||
await deleteDoc(doc(db, getPath.campaign(campaignId)));
|
||||
await storage.deleteDoc(getPath.campaign(campaignId));
|
||||
|
||||
if (selectedCampaignId === campaignId) {
|
||||
setSelectedCampaignId(null);
|
||||
}
|
||||
|
||||
const activeDisplayRef = doc(db, getPath.activeDisplay());
|
||||
const activeDisplaySnap = await getDoc(activeDisplayRef);
|
||||
const activeDisplay = await storage.getDoc(getPath.activeDisplay());
|
||||
|
||||
if (activeDisplaySnap.exists() && activeDisplaySnap.data().activeCampaignId === campaignId) {
|
||||
await updateDoc(activeDisplayRef, {
|
||||
if (activeDisplay && activeDisplay.activeCampaignId === campaignId) {
|
||||
await storage.updateDoc(getPath.activeDisplay(), {
|
||||
activeCampaignId: null,
|
||||
activeEncounterId: null
|
||||
});
|
||||
@@ -2672,13 +2674,14 @@ function LogsView() {
|
||||
const [undoingId, setUndoingId] = useState(null);
|
||||
|
||||
const handleClearLogs = async () => {
|
||||
if (!db) return;
|
||||
try {
|
||||
const snapshot = await getDocs(collection(db, getPath.logs()));
|
||||
if (!snapshot.empty) {
|
||||
const batch = writeBatch(db);
|
||||
snapshot.docs.forEach(d => batch.delete(d.ref));
|
||||
await batch.commit();
|
||||
const logs = await storage.getCollection(getPath.logs());
|
||||
if (logs.length > 0) {
|
||||
const ops = logs.map(l => {
|
||||
const id = l.id || l.path?.split('/').pop();
|
||||
return { type: 'delete', path: `${getPath.logs()}/${id}` };
|
||||
});
|
||||
await storage.batchWrite(ops);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error clearing logs:', err);
|
||||
@@ -2690,8 +2693,8 @@ function LogsView() {
|
||||
if (!db || !entry.undo) return;
|
||||
setUndoingId(entry.id);
|
||||
try {
|
||||
await updateDoc(doc(db, entry.undo.encounterPath), entry.undo.updates);
|
||||
await updateDoc(doc(db, getPath.logs(), entry.id), { undone: true });
|
||||
await storage.updateDoc(entry.undo.encounterPath, entry.undo.updates);
|
||||
await storage.updateDoc(`${getPath.logs()}/${entry.id}`, { undone: true });
|
||||
} catch (err) {
|
||||
console.error('Error undoing action:', err);
|
||||
alert('Failed to roll back. The encounter may have changed or no longer exists.');
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
import { initializeApp } from 'firebase/app';
|
||||
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';
|
||||
import {
|
||||
getFirestore, doc, setDoc, updateDoc, deleteDoc, addDoc, collection,
|
||||
onSnapshot, query, orderBy, limit, writeBatch, serverTimestamp,
|
||||
getFirestore, doc, setDoc, getDoc as getDocReal, getDocs as getDocsReal, addDoc, collection,
|
||||
onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, serverTimestamp,
|
||||
} from 'firebase/firestore';
|
||||
|
||||
// Path helpers mirror App.js getPath object.
|
||||
@@ -74,7 +74,7 @@ export function createFirebaseStorage() {
|
||||
|
||||
return {
|
||||
async getDoc(path) {
|
||||
const snap = await import('firebase/firestore').then(({ getDoc: gd, doc: d }) => gd(d(db, path)));
|
||||
const snap = await getDocReal(doc(db, path));
|
||||
return snap.exists() ? { id: snap.id, ...snap.data() } : null;
|
||||
},
|
||||
|
||||
@@ -96,7 +96,7 @@ export function createFirebaseStorage() {
|
||||
},
|
||||
|
||||
async getCollection(collectionPath) {
|
||||
const snapshot = await import('firebase/firestore').then(({ getDocs: gd, collection: c }) => gd(c(db, collectionPath)));
|
||||
const snapshot = await getDocsReal(collection(db, collectionPath));
|
||||
return snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user