| name | firebase-patterns |
| description | Firebase integration patterns for CJS2026 - Cloud Functions, Firestore operations, security rules, and authentication flows |
Firebase Patterns
When to Activate
Use this skill when the agent needs to:
- Create or modify Cloud Functions
- Write Firestore security rules
- Debug authentication issues
- Understand the Firebase ↔ React integration
- Handle Eventbrite webhook integration
Firebase Services Used
| Service | Purpose | Key Files |
|---|
| Auth | User authentication (Google, Magic Link) | AuthContext.jsx |
| Firestore | User profiles, logs, bookmarks | firestore.rules |
| Functions | Server-side logic, webhooks | functions/index.js |
| Storage | Profile photos | storage.rules |
| Hosting | Site deployment | firebase.json |
Cloud Function Patterns
Function Declaration Pattern
exports.functionName = onRequest(
{
cors: true,
secrets: [airtableApiKey]
},
async (req, res) => {
}
);
Authentication Levels
const auth = await verifyAuthToken(req.headers.authorization);
if (!auth) {
return res.status(401).json({ error: "Unauthorized" });
}
const admin = await requireAdmin(req);
const superAdmin = await requireSuperAdmin(req);
Error Handling Pattern
try {
await logActivity('action_type', userId, { details });
res.json({ success: true, data });
} catch (error) {
await logError('functionName', error, { context });
res.status(500).json({ error: error.message });
}
Logging Collections
| Collection | Purpose | Write Access |
|---|
activity_logs | User actions | Cloud Functions |
admin_logs | Admin actions | Cloud Functions |
system_errors | Error tracking | Cloud Functions |
background_jobs | Job execution | Cloud Functions |
Firestore Security Rules Pattern
User Profile Rules
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
allow read: if isAdmin();
allow update: if isAdmin() &&
!request.resource.data.diff(resource.data).affectedKeys().hasAny(['role']);
allow update: if isSuperAdmin();
}
Admin-Only Collections
match /activity_logs/{doc} {
allow read: if isAdmin();
allow write: if false;
}
User Profile Schema
Every user document has this structure (initialized on creation):
{
email, displayName, photoURL,
organization, jobTitle,
website, instagram, linkedin, bluesky,
registrationStatus: 'pending' | 'registered' | 'confirmed',
role: null | 'admin' | 'super_admin',
eventbriteAttendeeId, eventbriteOrderId,
ticketsPurchased, ticketType,
savedSessions: [],
scheduleVisibility: 'private' | 'attendees_only' | 'public',
attendedSummits: [], badges: [], customBadges: {},
createdAt, updatedAt
}
Safe Document Operations
Critical pattern: Always ensure document exists before merge operations.
async function ensureUserDocumentExists(uid, email) {
const docRef = doc(db, 'users', uid);
const docSnap = await getDoc(docRef);
if (!docSnap.exists()) {
await setDoc(docRef, {
...getEmptyProfileSchema(),
email,
createdAt: serverTimestamp(),
});
}
}
await ensureUserDocumentExists(uid, email);
await setDoc(userRef, updates, { merge: true });
Bookmark Count Sync
Atomic increment/decrement pattern:
await setDoc(
doc(db, 'sessionBookmarks', sessionId),
{ count: increment(1), updatedAt: serverTimestamp() },
{ merge: true }
);
await setDoc(
doc(db, 'sessionBookmarks', sessionId),
{ count: increment(-1), updatedAt: serverTimestamp() },
{ merge: true }
);
Admin Authorization Check
Dual system for backwards compatibility:
const ADMIN_EMAILS = [
"jamditis@gmail.com",
"murrayst@montclair.edu",
"etiennec@montclair.edu",
];
async function isAdmin(uid, email = null) {
if (email && ADMIN_EMAILS.includes(email.toLowerCase())) {
return true;
}
const userDoc = await db.collection('users').doc(uid).get();
const role = userDoc.data()?.role;
return role === 'admin' || role === 'super_admin';
}
Integration Points
- cjs-architecture - For understanding full system flow
- cms-content-pipeline - For Airtable sync functions
Snapshot Listener Best Practices
Auth Timing for Protected Collections
Problem: Queries to protected collections fail with permission-denied if the listener starts before auth loads.
useEffect(() => {
const unsubscribe = onSnapshot(query, (snapshot) => { ... })
return () => unsubscribe()
}, [])
useEffect(() => {
if (!currentUser) {
setLoading(false)
return
}
const unsubscribe = onSnapshot(
query,
(snapshot) => { },
(error) => {
console.error('Snapshot error:', error)
}
)
return () => unsubscribe()
}, [currentUser])
Always Add Error Handlers
const unsubscribe = onSnapshot(
query,
(snapshot) => {
},
(error) => {
console.error('[ComponentName] Firestore error:', error)
setLoading(false)
}
)
Guidelines
- Always use
verifyAuthToken() for authenticated endpoints
- Log errors with
logError() helper for admin visibility
- Use
logActivity() for user-facing actions
- Use
logAdminAction() for admin operations (audit trail)
- Test security rules locally before deploying
- Wait for auth state before querying protected collections
- Always add error handlers to onSnapshot listeners
- Deploy rules with
firebase deploy --only firestore:rules