User Data Segregation
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
User-Level Data Segregation
Last Updated: October 10, 2025
Overview
The GoGrab Space ecosystem implements complete user-level data segregation, ensuring that each user's data is isolated and secure. Data is automatically scoped by both userId and spaceId, allowing for multi-user, multi-space applications.
Key Concepts
User Context
The UserContextService manages the current user and space context:
interface UserContext {
userId: string | null;
spaceId: string | null;
displayName?: string;
permissions: string[];
isAuthenticated: boolean;
}
Scoped Keys
All storage keys are automatically prefixed with user and space information:
Format: user/{userId}/space/{spaceId}/{key}
Example: user/abc123/space/main/pages/home
User Databases
Each user/space combination gets its own database instance:
Database Name: user-{userId}-space-{spaceId}-{collection}
Example: user-abc123-space-main-pages
Implementation
1. Setting Up User Context
import { UserContextService } from '@storage-system';
// Inject service
private userContext = inject(UserContextService);
// Set user context (usually after login)
this.userContext.setUserContext({
userId: 'user-123',
spaceId: 'my-space',
displayName: 'John Doe',
permissions: ['read', 'write', 'admin']
});
// Switch active space
this.userContext.setActiveSpace('another-space');
// Clear context (logout)
this.userContext.clearUserContext();
2. Using Scoped Storage
import { UserScopedStorage } from '@storage-system';
// Inject scoped storage
private storage = inject(UserScopedStorage);
// Put data (automatically scoped)
await this.storage.put('my-data', { value: 123 });
// Stored as: user/user-123/space/my-space/my-data
// Get data (automatically scoped)
const data = await this.storage.get('my-data');
// List keys (automatically unscoped for display)
const keys = await this.storage.keys();
// Returns: ['my-data', 'another-key']
// (Not the full scoped keys)
3. Manual Key Scoping
If you need to manually scope keys:
import { scopeKey, unscopeKey } from '@storage-system';
// Scope a key
const scopedKey = scopeKey('pages/home', 'user-123', 'my-space');
// Result: "user/user-123/space/my-space/pages/home"
// Unscope a key
const components = unscopeKey(scopedKey);
// Result: { userId: 'user-123', spaceId: 'my-space', key: 'pages/home' }
4. Per-User Databases
import { UserDatabaseManager } from '@storage-system';
// Inject manager
private dbManager = inject(UserDatabaseManager);
// Get database for current user/space
const db = await this.dbManager.getCurrentUserDatabase('pages');
// Get database for specific user/space
const specificDb = await this.dbManager.getUserDatabase({
userId: 'user-123',
spaceId: 'my-space',
collection: 'pages',
namespace: 'user-user-123-space-my-space-pages',
adapters: ['dexie', 'localStorage']
});
// Get statistics
const stats = this.dbManager.getStatsForUserSpace('user-123', 'my-space');
console.log({
totalDatabases: stats.totalDatabases,
totalRecords: stats.totalRecords,
totalSizeBytes: stats.totalSizeBytes
});
Providing User-Scoped Storage
In App Config
import { ApplicationConfig } from '@angular/core';
import { provideUserScopedStorage } from '@storage-system';
export const appConfig: ApplicationConfig = {
providers: [
provideUserScopedStorage({
baseNamespace: 'my-app',
cache: { maxItems: 1000, ttlMs: 300000 },
strategy: 'balanced',
autoScope: true
})
]
};
In Component
import { Component, inject } from '@angular/core';
import { UserScopedStorage, UserContextService } from '@storage-system';
@Component({
selector: 'app-my-component',
standalone: true,
template: `...`
})
export class MyComponent {
private storage = inject(UserScopedStorage);
private userContext = inject(UserContextService);
async saveData() {
// Data is automatically scoped to current user/space
await this.storage.put('settings', {
theme: 'dark',
language: 'en'
});
}
async loadData() {
const settings = await this.storage.get('settings');
return settings;
}
}
Cross-Tab Synchronization
User context is automatically synchronized across browser tabs:
// Tab 1: User logs in
userContext.setUserContext({ userId: 'user-123', spaceId: 'main' });
// Tab 2: Context is automatically updated
// No manual synchronization needed!
This uses:
BroadcastChannelAPI (modern browsers)localStorageevents (fallback)- Custom events (additional fallback)
Space Management
Creating Spaces
// Spaces are typically managed server-side
// But you can work with them locally:
interface SpaceInfo {
id: string;
name: string;
description?: string;
ownerId: string;
members: Array<{
userId: string;
role: 'owner' | 'admin' | 'member' | 'viewer';
joinedAt: Date;
}>;
createdAt: Date;
updatedAt: Date;
}
Switching Spaces
// Get available spaces for user
const spaces = userContext.availableSpaces();
// Switch to a space
userContext.setActiveSpace('space-id');
// Current space is automatically used for all storage operations
Security Considerations
1. Authentication Required
Always ensure users are authenticated before accessing data:
if (!userContext.isAuthenticated()) {
router.navigate(['/login']);
return;
}
2. Permission Checks
Verify permissions before sensitive operations:
if (!userContext.hasPermission('admin')) {
console.error('Insufficient permissions');
return;
}
3. Data Cleanup
Clear user data on logout:
userContext.clearUserContext();
await dbManager.cleanupUserSpace(userId, spaceId);
Performance
- Key Scoping Overhead: < 0.1ms per operation
- Context Switching: < 5ms
- Cross-Tab Sync: < 10ms
- Database Creation: < 50ms (first time)
Best Practices
✅ Do
- Always use
UserScopedStorageinstead of rawGgStorage - Let the system handle key scoping automatically
- Check authentication before data operations
- Use
userContext.setActiveSpace()for space switching
❌ Don't
- Manually construct scoped keys (use
scopeKey()) - Access other users' data without permission
- Store sensitive data without encryption
- Forget to cleanup on logout
Examples
Complete User Flow
// 1. User Registration/Login
userContext.setUserContext({
userId: 'new-user-123',
spaceId: 'default',
displayName: 'Jane Smith',
email: '[email protected]',
permissions: ['read', 'write']
});
// 2. Save User Preferences
await storage.put('preferences', {
theme: 'dark',
notifications: true,
language: 'en'
});
// 3. Create Content
await storage.put('pages/my-first-page', {
title: 'My First Page',
content: '...'
});
// 4. Switch Space
userContext.setActiveSpace('work-space');
// 5. Content in new space is separate
const workPages = await storage.keys({ startsWith: 'pages/' });
// Only shows pages from work-space, not default space
// 6. Logout
userContext.clearUserContext();
Troubleshooting
Data Not Appearing
Issue: Data saved but not showing up
Solution:
// Check current context
console.log(userContext.userContext());
// Verify scoped key
const scopedKey = userContext.getScopedKey('my-data');
console.log('Looking for:', scopedKey);
// Check actual storage
const raw = await storage.getRawStorage().get(scopedKey);
console.log('Found:', raw);
Wrong User Data Showing
Issue: Seeing another user's data
Solution:
// Clear context and re-authenticate
userContext.clearUserContext();
// Logout and login again
// Verify context after login
console.log(userContext.userContext());
Migration Guide
From Non-Scoped to Scoped Storage
// Old code (non-scoped)
await ggStorage.put('pages/home', pageData);
// New code (scoped)
await userScopedStorage.put('pages/home', pageData);
// Automatically becomes: user/{userId}/space/{spaceId}/pages/home
Migrating Existing Data
// Get old data
const oldKeys = await oldStorage.keys();
// Re-save with scoping
for (const key of oldKeys) {
const data = await oldStorage.get(key);
await userScopedStorage.put(key, data);
}
See Also: