PAGES ARCHITECTURE

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Pages Data Architecture 📄

Date: November 24, 2025
Purpose: Define the complete data model and storage strategy for pages across the platform


🎯 Goals

  1. Separation of Concerns: Pages are independent from Universes
  2. Cross-Reference: Universes reference published pages
  3. Draft System: Support drafts, published versions, and history
  4. Performance: Optimized storage and retrieval
  5. MongoDB Structure: Proper collections and relationships

📊 Data Model

1. Pages Collection (MongoDB)

Collection Name: pages
Purpose: Store all pages across all spaces

interface Page {
  // Identity
  _id: string;                        // MongoDB ObjectId
  id: string;                         // User-friendly ID: `page-{spaceId}-{slug}-{timestamp}`
  
  // Metadata
  title: string;                      // "Home Page", "About Us", etc.
  slug: string;                       // URL-friendly: "home", "about-us"
  description: string;                // SEO description
  
  // Content
  widgets: Widget[];                  // Array of widget configurations
  layout: PageLayout;                 // Layout configuration (zones, responsive)
  
  // Ownership & Context
  spaceId: string;                    // Which space this page belongs to
  ownerId: string;                    // User who created it
  
  // Status & Publishing
  status: 'draft' | 'published' | 'archived';
  version: number;                    // Current version number
  publishedVersion?: number;          // Last published version (if published)
  
  // Timestamps
  createdAt: string;                  // ISO 8601
  updatedAt: string;                  // ISO 8601
  publishedAt?: string;               // ISO 8601 (when last published)
  
  // SEO & Meta
  meta: {
    title?: string;                   // Page title for SEO
    description?: string;             // Meta description
    keywords?: string[];              // SEO keywords
    ogImage?: string;                 // Open Graph image URL
    robots?: string;                  // robots meta tag
  };
  
  // Permissions (who can view/edit)
  permissions: {
    visibility: 'public' | 'private' | 'restricted';
    allowedUsers?: string[];          // User IDs who can access
    allowedRoles?: string[];          // Roles who can access
  };
  
  // Thumbnail (for page selector)
  thumbnail?: {
    url: string;                      // Screenshot URL
    generatedAt: string;              // When thumbnail was generated
  };
  
  // Version History (optional - for advanced features)
  history?: PageVersion[];
}

interface PageVersion {
  version: number;
  widgets: Widget[];
  layout: PageLayout;
  savedAt: string;
  savedBy: string;
  notes?: string;
}

interface Widget {
  id: string;
  type: string;                       // 'button', 'text', 'image', etc.
  config: Record<string, unknown>;    // Widget-specific configuration
  position: {
    zoneId: string;                   // Which layout zone
    order: number;                    // Order within zone
  };
  styles: Record<string, unknown>;    // CSS styles
  events?: WidgetEvent[];             // Singularity flows
}

interface PageLayout {
  type: 'flexbox' | 'grid' | 'custom';
  zones: LayoutZone[];
  responsive: ResponsiveConfig;
}

interface LayoutZone {
  id: string;
  name: string;
  type: 'header' | 'content' | 'sidebar' | 'footer' | 'custom';
  flexConfig?: FlexConfig;
  gridConfig?: GridConfig;
}

2. Universes Collection (MongoDB)

Collection Name: universes
Purpose: Store universe structures (sections, modules, layouts)

interface Universe {
  _id: string;
  id: string;                         // `universe-{spaceId}-{timestamp}`
  
  // Metadata
  name: string;
  slug: string;
  description: string;
  
  // Structure
  rootSection: CanvasSection;         // Nested tree structure
  
  // Context
  spaceId: string;
  ownerId: string;
  
  // Publishing
  status: 'draft' | 'published' | 'archived';
  version: number;
  
  // Timestamps
  createdAt: string;
  updatedAt: string;
  publishedAt?: string;
}

interface CanvasSection {
  id: string;
  label: string;
  layout: 'flexbox' | 'template';
  
  // Content: Reference to Page (NOT embedded!)
  content?: {
    type: 'module' | 'widget';
    moduleId: string;                 // e.g., 'page-renderer', 'footer'
    instanceId: string;
    configuration: {
      pageId?: string;                // 🔗 REFERENCE to Pages collection
      // ... other module config
    };
  };
  
  // Children sections
  children: CanvasSection[];
  
  // Template regions (if layout === 'template')
  regions?: TemplateRegion[];
}

3. Storage Keys (IndexedDB / GgStorage)

Local Storage (Client-side caching with GgStorage):

Key Pattern                          Purpose                          Data Type
───────────────────────────────────────────────────────────────────────────────
pages                                All pages for current space      Page[]
pages:drafts                         User's draft pages               Page[]
pages:{spaceId}                      Pages filtered by space          Page[]
editor/current-page                  Currently editing page           Page
editor/auto-save                     Auto-saved draft                 Page
universes:{spaceId}                  Universes for space              Universe[]
universe:{universeId}                Specific universe                Universe

🔄 Data Flow

Creating a Page

1. User opens Editor (/space/{spaceId}/editor)
2. Editor creates draft: { id: 'page-draft-{timestamp}', status: 'draft' }
3. User adds widgets → auto-saved to GgStorage: 'editor/auto-save'
4. User clicks "Save Draft" → saved to MongoDB: pages collection
5. User clicks "Publish" → status: 'published', publishedAt: now()

Using a Page in Universe

1. User drops "Page Renderer" module into canvas
2. User clicks module → Config panel opens
3. User clicks "Select Page" → Page Selector modal
4. Page Selector loads from:
   a. Local cache (GgStorage: 'pages')
   b. If empty, fetch from MongoDB
5. User selects page → pageId set in section.content.configuration
6. Universe saved with reference:
   {
     content: {
       moduleId: 'page-renderer',
       configuration: { pageId: 'page-home-123' }  // 🔗 Reference
     }
   }
7. When rendering, Page Renderer loads page by ID from MongoDB

🗄️ MongoDB Collections Structure

Database: gograbspace
│
├── spaces/                          # Spaces (tenants)
│   └── { _id, name, slug, ownerId, createdAt, ... }
│
├── pages/                           # ✨ NEW: Pages collection
│   ├── { _id, id, spaceId, title, widgets, status: 'draft', ... }
│   ├── { _id, id, spaceId, title, widgets, status: 'published', ... }
│   └── ... (indexed by spaceId, status, slug)
│
├── universes/                       # Universes (canvas structures)
│   └── { _id, id, spaceId, rootSection, ... }
│
└── users/                           # Users
    └── { _id, email, name, ... }

Indexes:

// pages collection
db.pages.createIndex({ spaceId: 1, status: 1 });
db.pages.createIndex({ spaceId: 1, slug: 1 });
db.pages.createIndex({ id: 1 }, { unique: true });
db.pages.createIndex({ ownerId: 1, createdAt: -1 });

// universes collection
db.universes.createIndex({ spaceId: 1 });
db.universes.createIndex({ id: 1 }, { unique: true });

🎯 Implementation Checklist

Phase 1: Foundation ✅

Phase 2: Editor Integration 🚧

Phase 3: MongoDB Backend 🚧

Phase 4: Universe Integration ✅

Phase 5: Advanced Features ⏳


🔧 Migration Strategy

From Current State → To New Architecture

Step 1: Unify Storage (Immediate)

// OLD: Two separate storage approaches
EditorModule: await this.storage.put(`editor/pages/${pageId}`, page);
PagesService: await this.storage.put('pages', pagesArray);

// NEW: Single source of truth
PagesService.createPage() → saves to 'pages' array AND syncs to backend
PagesService.updatePage() → updates 'pages' array AND syncs to backend

Step 2: Add Backend Sync (This Week)

// PagesService enhancement
async createPage(page: Partial<Page>): Promise<Page> {
  const newPage = { ...page, id: generateId(), status: 'draft', ... };
  
  // 1. Save locally (immediate)
  const pages = [...this._pages(), newPage];
  await this.storage.put('pages', pages);
  this._pages.set(pages);
  
  // 2. Sync to backend (background)
  this.syncToBackend(newPage).subscribe();
  
  return newPage;
}

private syncToBackend(page: Page): Observable<void> {
  return this.http.post<void>(`/api/spaces/${page.spaceId}/pages`, page).pipe(
    tap(() => this.logger.info('Page synced to backend', { pageId: page.id })),
    catchError(error => {
      this.logger.error('Failed to sync page', { error });
      // Queue for retry
      return EMPTY;
    })
  );
}

Step 3: Editor Integration (This Week)

// EditorUiComponent: Replace download-only save
onSave(): void {
  const currentPage = this.activePage();
  
  if (currentPage) {
    // Update page with current widgets
    const updatedPage = {
      ...currentPage,
      widgets: this.widgets(),
      updatedAt: new Date().toISOString(),
      version: currentPage.version + 1
    };
    
    // Save via PagesService (unified)
    this.pagesService.updatePage(currentPage.id, updatedPage)
      .then(() => {
        this.editorService.setStatusMessage('Page saved successfully');
      })
      .catch(error => {
        this.editorService.setStatusMessage('Error saving page');
        console.error(error);
      });
  }
}

📝 Example Queries

Get all published pages for a space

const pages = await pagesService.getPagesBySpace('space-123', { status: 'published' });

Get page by slug

const page = await pagesService.getPageBySlug('space-123', 'home');

Load page in Universe

// In Page Renderer component
const pageId = this.configuration().pageId;
if (pageId) {
  const page = await this.pagesService.getPage(pageId);
  this.renderPage(page);
}

🚀 Performance Considerations

  1. Caching Strategy

    • Local cache (GgStorage) for immediate access
    • Background sync with MongoDB
    • Cache TTL: 5 minutes for published pages, 30s for drafts
  2. Lazy Loading

    • Load page metadata (title, description) first
    • Load full widgets on demand
  3. Pagination

    • Page Selector loads 20 pages at a time
    • Infinite scroll for large libraries
  4. Thumbnails

    • Generate on publish (background job)
    • Store in CDN (if available) or MongoDB GridFS

End of Document