firestore-operations
Skill from retex73/music-app
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
# Firestore Operations Skill
## Purpose
Master Firestore database operations including CRUD, real-time listeners, and data modeling for the music-app project.
## Collections Structure
```
favorites/{userId}
└── { hatao: [ids...], thesession: [ids...] }
tunePreferences/{userId_tuneId}
└── { userId, tuneId, versionOrder: [ids...], updatedAt }
```
## Core Patterns
### Read Operations
```javascript
import { doc, getDoc, collection, getDocs, query, where } from 'firebase/firestore';
import { db } from '../config/firebase';
// Get single document
const docRef = doc(db, 'favorites', userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data();
} else {
// Document doesn't exist
}
// Query collection
const q = query(
collection(db, 'tunePreferences'),
where('userId', '==', userId)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});
```
### Write Operations
```javascript
import { doc, setDoc, updateDoc, deleteDoc } from 'firebase/firestore';
// Create/overwrite document
await setDoc(doc(db, 'favorites', userId), {
hatao: [tuneId1, tuneId2],
thesession: []
});
// Update specific fields
await updateDoc(doc(db, 'favorites', userId), {
'hatao': arrayUnion(newTuneId) // Add to array
});
// Delete document
await deleteDoc(doc(db, 'favorites', userId));
```
### Real-Time Listeners
```javascript
import { onSnapshot } from 'firebase/firestore';
useEffect(() => {
const unsubscribe = onSnapshot(
doc(db, 'favorites', userId),
(docSnap) => {
if (docSnap.exists()) {
setFavorites(docSnap.data().hatao || []);
}
},
(error) => {
console.error('Listener error:', error);
}
);
// Clean up listener
return () => unsubscribe();
}, [userId]);
```
### Service Layer Pattern
```javascript
// src/services/favouritesService.js
export const getFavorites = async (userId, source = 'hatao') => {
try {
const docRef = doc(db, 'favorites', userId);
const docSnap = await getDoc(docRef);
return docSnap.exists() ? (docSnap.data()[source] || []) : [];
} catch (error) {
console.error('Error fetching favorites:', error);
throw error;
}
};
export const addFavorite = async (userId, tuneId, source = 'hatao') => {
try {
const docRef = doc(db, 'favorites', userId);
const docSnap = await getDoc(docRef);
let data = docSnap.exists() ? docSnap.data() : {};
if (!data[source]) data[source] = [];
if (!data[source].includes(tuneId)) {
data[source].push(tuneId);
await setDoc(docRef, data);
}
} catch (error) {
console.error('Error adding favorite:', error);
throw error;
}
};
```
## Best Practices
✅ **Do:**
- Wrap operations in try-catch
- Validate data before writing
- Clean up onSnapshot listeners
- Use batch writes for multiple updates
- Check user authentication before operations
❌ **Don't:**
- Forget error handling
- Leave listeners running
- Write without validation
- Query without limits (for large datasets)
- Store sensitive data unencrypted
## Security Rules Example
```javascript
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /favorites/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
```
## Common Issues
**Issue:** Permission denied errors
**Solution:** Check security rules and user authentication
**Issue:** Data not syncing in real-time
**Solution:** Verify onSnapshot setup and cleanup
**Issue:** Writes failing silently
**Solution:** Add try-catch and log errors