File & Folder Filtering Guide with Sharing Support
Overview
This guide explains how to properly use the FileSystemCache and EnhancedFileSystemCache APIs for file and folder filtering with the new sharing structure. The cache supports both room owners (who can see all their files) and non-owners (who see room-specific files).
Table of Contents
- Cache Initialization
- Room Owner Filtering
- Understanding File Inclusion Rules
- Complete Implementation Examples
- API Reference
- Common Pitfalls
Cache Initialization
Setting Up the Cache
import { getFileSystemCache } from './api/FileSystemCache.js';
import { EnhancedFileSystemCache } from './api/EnhancedFileSystemCache.js';
// Get cache instance
const cache = getFileSystemCache({
client: apiClient,
userId: currentUser.id,
roomName: currentRoom,
isRoomOwner: isCurrentUserRoomOwner // CRITICAL: Must be set correctly!
});
Important: The isRoomOwner parameter determines:
- Room Owners: Get one cache with ALL files across ALL rooms
- Non-Owners: Get room-specific cache with filtered files
Room Owner Filtering
Loading Filtered Content
Room owners can toggle between viewing all their files or just files for the current room:
async function loadFolderContents(folderPath) {
const isRoomOwner = true; // Determined from your auth/room logic
const showRoomFilesOnly = getUserPreference('showRoomFilesOnly');
if (isRoomOwner && showRoomFilesOnly) {
// Option A: Get filtered tree (recommended - includes shared files)
const filteredTree = await EnhancedFileSystemCache.getFilteredFolderTreeWithInit(
cache,
currentRoom
);
// Option B: Or use the cache method directly
const roomSpecificTree = await cache.getFileTreeByRoom(currentRoom);
// Extract folder contents from filtered tree
const folderNode = extractFolderFromTree(filteredTree, folderPath);
return folderNode;
} else {
// Show all files (no filtering)
const fullTree = await EnhancedFileSystemCache.getFileTreeWithInit(cache);
const folderNode = extractFolderFromTree(fullTree, folderPath);
return folderNode;
}
}
Understanding File Inclusion Rules
What Gets Included When Filtering by Room
When filtering is applied, files are included based on these rules:
function isFileIncludedInRoomFilter(file, targetRoom, currentUserId) {
// 1. Files that belong to the room directly
if (file.roomName === targetRoom) {
return true;
}
// 2. Files shared WITH the current user IN this room
if (file.sharing && Array.isArray(file.sharing)) {
return file.sharing.some(share =>
share.userId === currentUserId &&
share.roomName === targetRoom
);
}
return false;
}
Sharing Structure
The new sharing structure provides granular control:
// File sharing structure
{
sharing: [{
userId: "user123",
roomName: "popsyroom1",
permissions: ["read", "write"]
}]
}
Complete Implementation Examples
Vanilla JavaScript FileManager
class FileManager {
constructor(apiClient, currentUser, currentRoom, isRoomOwner) {
this.cache = getFileSystemCache({
client: apiClient,
userId: currentUser.id,
roomName: currentRoom,
isRoomOwner: isRoomOwner // MUST be set!
});
this.currentRoom = currentRoom;
this.currentUser = currentUser;
this.isRoomOwner = isRoomOwner;
this.showRoomFilesOnly = false; // Toggle state
}
async loadFolder(folderPath = '/') {
try {
let tree;
// Room owners can filter by room
if (this.isRoomOwner && this.showRoomFilesOnly) {
// Use filtered tree that includes shared files
tree = await EnhancedFileSystemCache.getFilteredFolderTreeWithInit(
this.cache,
this.currentRoom
);
console.log('📁 Using filtered tree for room:', this.currentRoom);
} else {
// Use full tree
tree = await EnhancedFileSystemCache.getFileTreeWithInit(this.cache);
console.log('📁 Using full tree (no filtering)');
}
// Navigate to the folder in the tree
const folder = this.navigateToPath(tree, folderPath);
// Get files and folders
const contents = {
files: [],
folders: []
};
if (folder && folder.children) {
Object.values(folder.children).forEach(child => {
if (child.type === 'folder') {
contents.folders.push(child);
} else {
contents.files.push(child);
}
});
}
return contents;
} catch (error) {
console.error('Error loading folder:', error);
throw error;
}
}
// Helper to navigate tree to a specific path
navigateToPath(tree, targetPath) {
if (!targetPath || targetPath === '/') {
return tree;
}
const segments = targetPath.split('/').filter(Boolean);
let current = tree;
for (const segment of segments) {
if (!current.children) return null;
current = Object.values(current.children).find(child =>
child.name === segment && child.type === 'folder'
);
if (!current) return null;
}
return current;
}
// Check if a file is shared in the current room
isFileShared(file) {
if (!file.sharing || !Array.isArray(file.sharing)) {
return false;
}
// Check if file is shared with anyone in current room
return file.sharing.some(share =>
share.roomName === this.currentRoom
);
}
// Check if file is shared with current user
isFileSharedWithMe(file) {
if (!file.sharing || !Array.isArray(file.sharing)) {
return false;
}
return file.sharing.some(share =>
share.userId === this.currentUser.id &&
share.roomName === this.currentRoom
);
}
// Toggle room filtering
toggleRoomFiltering() {
this.showRoomFilesOnly = !this.showRoomFilesOnly;
// Reload current folder with new filtering
this.loadFolder(this.currentPath);
}
}
React Component Example
function FileExplorer({ apiClient, currentUser, currentRoom, isRoomOwner }) {
const [showRoomFilesOnly, setShowRoomFilesOnly] = useState(false);
const [folderContents, setFolderContents] = useState({ files: [], folders: [] });
const [currentPath, setCurrentPath] = useState('/');
// Initialize cache with proper room ownership
const cache = useMemo(() => {
return getFileSystemCache({
client: apiClient,
userId: currentUser.id,
roomName: currentRoom,
isRoomOwner: isRoomOwner // Critical!
});
}, [apiClient, currentUser.id, currentRoom, isRoomOwner]);
// Load folder contents
const loadFolder = useCallback(async (path) => {
try {
let tree;
if (isRoomOwner && showRoomFilesOnly) {
// Filtered tree - includes owned files + shared files in this room
tree = await EnhancedFileSystemCache.getFilteredFolderTreeWithInit(
cache,
currentRoom
);
} else {
// Full tree - all files
tree = await EnhancedFileSystemCache.getFileTreeWithInit(cache);
}
// Extract folder at path
const folder = navigateToPath(tree, path);
// Convert to files/folders arrays
const contents = { files: [], folders: [] };
if (folder?.children) {
Object.values(folder.children).forEach(child => {
if (child.type === 'folder') {
contents.folders.push(child);
} else {
// Add sharing indicator
contents.files.push({
...child,
isShared: child.sharing?.length > 0,
sharedWithMe: child.sharing?.some(s =>
s.userId === currentUser.id &&
s.roomName === currentRoom
)
});
}
});
}
setFolderContents(contents);
} catch (error) {
console.error('Failed to load folder:', error);
}
}, [cache, currentRoom, showRoomFilesOnly, isRoomOwner, currentUser.id]);
// Reload when filter changes
useEffect(() => {
loadFolder(currentPath);
}, [currentPath, showRoomFilesOnly, loadFolder]);
return (
<div>
{isRoomOwner && (
<button onClick={() => setShowRoomFilesOnly(!showRoomFilesOnly)}>
{showRoomFilesOnly ? 'Show All Files' : 'Show Room Files Only'}
</button>
)}
<div className="folders">
{folderContents.folders.map(folder => (
<FolderItem
key={folder.id}
folder={folder}
onClick={() => setCurrentPath(folder.path)}
/>
))}
</div>
<div className="files">
{folderContents.files.map(file => (
<FileItem
key={file.id}
file={file}
isShared={file.isShared}
sharedWithMe={file.sharedWithMe}
/>
))}
</div>
</div>
);
}
// Helper function for navigating tree
function navigateToPath(tree, targetPath) {
if (!targetPath || targetPath === '/') {
return tree;
}
const segments = targetPath.split('/').filter(Boolean);
let current = tree;
for (const segment of segments) {
if (!current.children) return null;
current = Object.values(current.children).find(child =>
child.name === segment && child.type === 'folder'
);
if (!current) return null;
}
return current;
}
API Reference
Available Methods
| Method | Returns | Use Case |
|---|---|---|
getFileTree() |
All files in cache | Show everything (no filtering) |
getFileTreeByRoom(room) |
Files owned in room + files shared in room | Room filtering for owners |
getFilteredFolderTreeWithInit(cache, room) |
Recursive filtered tree | Deep folder filtering |
getFilesByRoom(room) |
Flat array of room files | Getting file list only |
folderContainsRoomFilesWithInit(cache, path, room) |
Boolean | Check if folder has room files |
Cache Methods for Room Owners
// Get all files (no filtering)
const allFiles = await cache.getAllFiles();
// Get files for specific room (includes shared files)
const roomFiles = await cache.getFilesByRoom('popsyroom1');
// Get filtered tree for room
const roomTree = await cache.getFileTreeByRoom('popsyroom1');
// Check if folder contains files from room
const hasRoomFiles = await EnhancedFileSystemCache.folderContainsRoomFilesWithInit(
cache,
'/Whiteboards',
'popsyroom1'
);
Cache Methods for Non-Owners
// Non-owners always get room-specific data
const files = await cache.getAllFiles(); // Returns only room files
const tree = await cache.getFileTree(); // Returns only room tree
Common Pitfalls
❌ Don't Do This
// WRONG - This doesn't filter folders properly
const tree = await cache.getFileTree();
const filtered = myCustomFilter(tree); // Don't do client-side filtering
// WRONG - Using old sharedWith field (removed)
const isShared = file.sharedWith?.includes(currentUser.id);
// WRONG - Not setting isRoomOwner
const cache = getFileSystemCache({
client: apiClient,
userId: currentUser.id,
roomName: currentRoom
// Missing isRoomOwner!
});
// WRONG - Not checking sharing structure properly
if (file.sharing) { // Need to check array and room
return true;
}
✅ Do This Instead
// CORRECT - Use proper filtering methods
if (isRoomOwner && showRoomFilesOnly) {
const tree = await cache.getFileTreeByRoom(currentRoom);
}
// CORRECT - Check sharing properly
const isSharedInThisRoom = file.sharing?.some(share =>
share.userId === currentUser.id &&
share.roomName === currentRoom
);
// CORRECT - Always set isRoomOwner
const cache = getFileSystemCache({
client: apiClient,
userId: currentUser.id,
roomName: currentRoom,
isRoomOwner: isCurrentUserRoomOwner // Required!
});
// CORRECT - Check both user and room in sharing
const hasAccess = file.sharing?.some(share =>
share.userId === currentUser.id &&
share.roomName === currentRoom
);
Key Concepts
Database Structure
- Room Owners: One database named
muzie-file-system-cache-${userId} - Non-Owners: Room-specific database named
muzie-file-system-cache-${userId}::${roomName}
Filtering Logic
The filtering works recursively:
- Check if file/folder belongs to the target room
- Check if file is shared with the current user in the target room
- Include folders only if they contain relevant files (at any depth)
Sharing Permissions
The sharing structure supports permissions for future features:
{
userId: "user123",
roomName: "room1",
permissions: ["read", "write", "delete"]
}
Currently, all shares default to ["read"] permissions.
Migration Notes
If migrating from the old system:
- Remove references to
sharedWith- This field has been completely removed - Update to use
sharingarray - The new structure with userId, roomName, permissions - Set
isRoomOwnercorrectly - Critical for proper cache behavior - Use new filtering methods - Don't implement custom filtering in the UI
Support
For questions or issues with the file system cache, please check:
- The cache implementation in
/src/api/FileSystemCache.js - The enhanced cache wrapper in
/src/api/EnhancedFileSystemCache.js - The test suite in
/src/api/__tests__/FileSystemCache.test.js