Rework backend #1

Merged
robert merged 86 commits from rework-backend into main 2026-07-01 19:29:34 -04:00
2 changed files with 59 additions and 46 deletions
Showing only changes of commit 5bb9e5fc19 - Show all commits
+15 -36
View File
@@ -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 } from './storage';
import { getFirestore, doc, setDoc, getDoc, getDocs, 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,
@@ -201,32 +201,22 @@ function useFirestoreDocument(docPath) {
const [error, setError] = useState(null);
useEffect(() => {
if (!db || !docPath) {
if (!docPath) {
setData(null);
setIsLoading(false);
setError(docPath ? "Firestore not available." : "Document path not provided.");
setError("Document path not provided.");
return;
}
setIsLoading(true);
setError(null);
const docRef = doc(db, docPath);
const unsubscribe = onSnapshot(
docRef,
(docSnap) => {
setData(docSnap.exists() ? { id: docSnap.id, ...docSnap.data() } : null);
const storage = getStorage();
const unsubscribe = storage.subscribeDoc(docPath, (doc) => {
setData(doc);
setIsLoading(false);
},
(err) => {
console.error(`Error fetching document ${docPath}:`, err);
setError(err.message || "Failed to fetch document.");
setIsLoading(false);
setData(null);
}
);
return () => unsubscribe();
});
return () => { if (typeof unsubscribe === 'function') unsubscribe(); };
}, [docPath]);
return { data, isLoading, error };
@@ -239,34 +229,23 @@ function useFirestoreCollection(collectionPath, queryConstraints = []) {
const queryString = useMemo(() => JSON.stringify(queryConstraints), [queryConstraints]);
useEffect(() => {
if (!db || !collectionPath) {
if (!collectionPath) {
setData([]);
setIsLoading(false);
setError(collectionPath ? "Firestore not available." : "Collection path not provided.");
setError("Collection path not provided.");
return;
}
setIsLoading(true);
setError(null);
const q = query(collection(db, collectionPath), ...queryConstraints);
const unsubscribe = onSnapshot(
q,
(snapshot) => {
const items = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
const storage = getStorage();
const unsubscribe = storage.subscribeCollection(collectionPath, (items) => {
setData(items);
setIsLoading(false);
},
(err) => {
console.error(`Error fetching collection ${collectionPath}:`, err);
setError(err.message || "Failed to fetch collection.");
setIsLoading(false);
setData([]);
}
);
return () => unsubscribe();
// We use queryString instead of queryConstraints to avoid re-renders on array reference changes
});
return () => { if (typeof unsubscribe === 'function') unsubscribe(); };
// queryString, not array ref
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collectionPath, queryString]);
+41 -7
View File
@@ -1,13 +1,47 @@
// src/storage/index.js — barrel re-export of Firebase SDK.
// Phase C: App.js swaps imports from 'firebase/*' to here.
// STORAGE=firebase = identical behavior. Zero risk.
// Later: factory picks impl based on REACT_APP_STORAGE env.
// src/storage/index.js — storage factory + SDK re-exports.
// STORAGE=firebase (default): adapter wraps SDK. STORAGE=ws: backend.
// App.js imports getStorage() for subscribe; still imports SDK pieces for writes (per-group refactor pending).
export { initializeApp } from 'firebase/app';
export {
import { initializeApp } from 'firebase/app';
import {
getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken,
} from 'firebase/auth';
export {
import {
getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection,
onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch,
} from 'firebase/firestore';
import { initFirebase, createFirebaseStorage } from './firebase';
let storageInstance = null;
// Returns adapter instance implementing interface (getDoc/setDoc/subscribeDoc/etc).
export function getStorage() {
if (storageInstance) return storageInstance;
const mode = process.env.REACT_APP_STORAGE || 'firebase';
if (mode === 'firebase') {
const ok = initFirebase();
if (!ok) throw new Error('Firebase config missing. Check REACT_APP_FIREBASE_* env.');
storageInstance = createFirebaseStorage();
} else if (mode === 'ws') {
const { createWsStorage } = require('./ws');
storageInstance = createWsStorage({
baseUrl: process.env.REACT_APP_BACKEND_URL || 'http://127.0.0.1:4001',
wsUrl: process.env.REACT_APP_BACKEND_WS || 'ws://127.0.0.1:4001/ws',
});
} else {
const { createMemoryStorage } = require('./memory');
storageInstance = createMemoryStorage();
}
return storageInstance;
}
export function getStorageMode() {
return process.env.REACT_APP_STORAGE || 'firebase';
}
export {
initializeApp,
getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken,
getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection,
onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch,
};