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:

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

Best Practices

✅ Do

❌ Don't

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: