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:
- Public read access: Themes are meant to be browsable by everyone (like a public catalog)
- Protected writes: Creating, updating, and deleting themes requires authentication
- Security: Users can only modify themes they own (enforced by backend logic)
✅ 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
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
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()
apis/backend-api/src/app/themes/themes.service.ts- Enhanced
findAll()logging - Added diagnostic warnings
- Enhanced
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
- Open GoGrab Space:
http://localhost:5200 - Navigate to Theme Designer
- Click "Theme Library" button
- 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:
Global Themes Collection (
themes)- Shared catalog accessible across all spaces
- Used by Theme Library for browsing
- Queried by
GET /api/themes
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
GET operations (read-only): Public access
- Rationale: Themes are like a public catalog that anyone can browse
POST/PUT/DELETE operations (write): Requires authentication
- Rationale: Only authenticated users can create/modify themes
- Additional check: Users can only modify their own themes (enforced by backend)
Verification Checklist
- ✅ Backend restarts without errors
- ✅
GET /api/themesreturns themes without auth - ✅ Theme Library displays global themes
- ✅ Backend logs show theme count
- ✅ Date formats are consistent (ISO strings)
- ✅ Write operations still require auth
- ✅ No linter errors
Related Files
- Theme Library Component:
libs/libs-space/theme-designer/src/lib/theme-library/ - Theme HTTP Storage:
libs/theme-system/theme-runtime/src/lib/api/theme-http-storage.service.ts - Backend API:
apis/backend-api/src/app/themes/ - Spaces Service:
apis/backend-api/src/app/spaces/spaces.service.ts
Future Improvements
- Pagination: Add pagination for large theme catalogs (currently limited to 500)
- Filtering: Add query parameters for filtering by mode, author, tags, etc.
- Search: Add full-text search for theme names and descriptions
- Ratings: Add user ratings and reviews for themes
- Tags: Add tagging system for better organization
Fixed: November 21, 2025
Session: 11.25.1.1
Status: ✅ Complete and tested