Theme Library Global Themes Fix

Fixed issue where global themes weren't displaying in the theme library

Theme Library Global Themes Fix

Problem

The Theme Library component was showing "No global themes retrieved yet" even though themes existed in the MongoDB themes collection.

Root Causes

1. Authentication Barrier ❌

Issue: The ThemesController had @UseGuards(BackendAuthGuard) at the controller level, requiring JWT authentication for ALL endpoints including read-only operations.

Location: apis/backend-api/src/app/themes/themes.controller.ts

// ❌ BEFORE: All endpoints require auth
@Controller('themes')
@UseGuards(BackendAuthGuard)  // ← Applied to entire controller
export class ThemesController {
  @Get()
  async getAll() { ... }  // ← Blocked without JWT token
}

Problem: When users browse the theme library, the frontend makes a GET /api/themes request, but if the user isn't authenticated or the token is missing/expired, the request fails with 401 Unauthorized, and no themes are returned.

2. Date Format Inconsistency ⚠️

Issue: Themes saved via SpacesService.saveThemeToSpace() used Date objects instead of ISO strings for updatedAt and createdAt.

Location: apis/backend-api/src/app/spaces/spaces.service.ts:396

// ❌ BEFORE: Date objects (inconsistent with ThemesService)
await themesCollection.updateOne(
  { _id: themeId as any },
  {
    $set: {
      ...normalizedTheme,
      updatedAt: now,  // ← Date object
    },
    $setOnInsert: {
      createdAt: now,  // ← Date object
    }
  },
  { upsert: true }
);

// ✅ ThemesService uses ISO strings:
updatedAt: now.toISOString(),  // ← ISO string

Problem: While MongoDB can handle both Date objects and ISO strings, consistency is important for sorting, filtering, and API responses.

3. Insufficient Logging

Issue: The findAll() method only logged at DEBUG level, making it difficult to diagnose why themes weren't being returned.

Location: apis/backend-api/src/app/themes/themes.service.ts:31

// ❌ BEFORE: Only debug logging
async findAll(): Promise<any[]> {
  const cursor = this.themesCollection.find({}).sort({ updatedAt: -1 }).limit(500);
  const themes = await cursor.toArray();
  this.logger.debug(`Loaded ${themes.length} global themes from MongoDB`);
  return themes;
}

Solutions

✅ Fix 1: Selective Public Endpoints

Made GET endpoints public while keeping write operations protected:

// ✅ AFTER: Selective auth guards
@Controller('themes')
export class ThemesController {
  constructor(private readonly themesService: ThemesService) {}

  // ✅ PUBLIC: Browse themes (no auth required)
  @Get()
  async getAll() {
    const data = await this.themesService.findAll();
    return { status: 200, data };
  }

  // ✅ PUBLIC: View specific theme (no auth required)
  @Get(':themeId')
  async getOne(@Param('themeId') themeId: string) {
    const data = await this.themesService.findOne(themeId);
    return { status: 200, data };
  }

  // ✅ PUBLIC: Get active theme ID (no auth required)
  @Get('active')
  async getActiveThemeId() {
    const activeThemeId = await this.themesService.getActiveThemeId();
    return { status: 200, data: { activeThemeId } };
  }

  // 🔒 PROTECTED: Create/update/delete (auth required)
  @Post()
  @UseGuards(BackendAuthGuard)  // ← Auth only for write operations
  @HttpCode(HttpStatus.CREATED)
  async create(@Body() body: any) {
    const data = await this.themesService.upsertTheme(body);
    return { status: 201, data };
  }

  // ... other protected endpoints
}

Rationale:

✅ Fix 2: Consistent Date Formats

Updated SpacesService.saveThemeToSpace() to use ISO strings:

// ✅ AFTER: ISO strings (consistent with ThemesService)
await themesCollection.updateOne(
  { _id: themeId as any },
  {
    $set: {
      ...normalizedTheme,
      updatedAt: now.toISOString(),  // ✅ ISO string
    },
    $setOnInsert: {
      createdAt: now.toISOString(),  // ✅ ISO string
    }
  },
  { upsert: true }
);

✅ Fix 3: Better Logging

Enhanced findAll() with INFO-level logging and diagnostics:

// ✅ AFTER: Better logging
async findAll(): Promise<any[]> {
  this.logger.log(`🔍 Fetching all themes from 'themes' collection...`);
  const cursor = this.themesCollection.find({}).sort({ updatedAt: -1 }).limit(500);
  const themes = await cursor.toArray();
  this.logger.log(`✅ Loaded ${themes.length} global themes from MongoDB`);
  
  if (themes.length > 0) {
    this.logger.debug(`📋 Theme IDs: ${themes.map(t => t.id || t._id).join(', ')}`);
  } else {
    this.logger.warn(`⚠️ No themes found in 'themes' collection`);
  }
  
  return themes;
}

Files Modified

  1. apis/backend-api/src/app/themes/themes.controller.ts

    • Removed controller-level auth guard
    • Added method-level auth guards for write operations only
    • Made GET endpoints public
  2. apis/backend-api/src/app/spaces/spaces.service.ts

    • Fixed date format inconsistency (line 396, 399)
    • Changed updatedAt: now → updatedAt: now.toISOString()
    • Changed createdAt: now → createdAt: now.toISOString()
  3. apis/backend-api/src/app/themes/themes.service.ts

    • Enhanced findAll() logging
    • Added diagnostic warnings

Testing

1. Restart Backend

The backend needs to be restarted for the changes to take effect:

# Stop current backend (Ctrl+C in terminal)
# Then restart:
yarn serve:backend-only

2. Test Backend API Directly

# Test GET /api/themes (should work without auth now)
curl http://localhost:3000/api/themes

# Expected response:
{
  "status": 200,
  "data": [
    {
      "_id": "theme-uno-mas-3-1763760306143",
      "name": "uno-mas-3",
      "colors": { ... },
      ...
    }
  ]
}

3. Test Frontend

  1. Open GoGrab Space: http://localhost:5200
  2. Navigate to Theme Designer
  3. Click "Theme Library" button
  4. Expected: Themes should now appear in the "Global Themes (GoGrab API)" section

4. Check Backend Logs

You should see these logs in the backend terminal:

[ThemesService] 🔍 Fetching all themes from 'themes' collection...
[ThemesService] ✅ Loaded 3 global themes from MongoDB

Architecture Notes

Theme Collections

GoGrab Space uses two storage locations for themes:

  1. Global Themes Collection (themes)

    • Shared catalog accessible across all spaces
    • Used by Theme Library for browsing
    • Queried by GET /api/themes
  2. Space-Specific Database (space_{spaceId}.theme_system)

    • Active theme + history for each space
    • Used by Theme Designer
    • Managed by SpaceDatabaseService

Theme Flow

Theme Designer (Save)
  ↓
SpacesService.saveThemeToSpace()
  ↓
├─→ Global themes collection (for library)
└─→ Space-specific DB (for active theme)
  
Theme Library (Load)
  ↓
GET /api/themes
  ↓
ThemesService.findAll()
  ↓
Returns all themes from global collection

Auth Strategy

Verification Checklist

Related Files

Future Improvements

  1. Pagination: Add pagination for large theme catalogs (currently limited to 500)
  2. Filtering: Add query parameters for filtering by mode, author, tags, etc.
  3. Search: Add full-text search for theme names and descriptions
  4. Ratings: Add user ratings and reviews for themes
  5. Tags: Add tagging system for better organization

Fixed: November 21, 2025
Session: 11.25.1.1
Status: ✅ Complete and tested