feat(dev): Add database seeding for development and testing

- Add seedLibraryItems function for populating test data
- Create sample library items with realistic content
- Add library controller for REST endpoints
- Support development workflow with consistent test data
This commit is contained in:
Timothy Atapagra 2025-10-04 16:47:40 -04:00
parent 41db30c856
commit 905d1892af
2 changed files with 189 additions and 0 deletions

View file

@ -0,0 +1,148 @@
import { randomUUID } from 'crypto'
import { DataSource } from 'typeorm'
import {
LibraryItemEntity,
LibraryItemState,
ContentReaderType,
} from '../../library/entities/library-item.entity'
/**
* Seed example library items for a user (for testing)
* Usage: Call this function after user registration in development
*/
export async function seedLibraryItems(
dataSource: DataSource,
userId: string,
): Promise<LibraryItemEntity[]> {
console.log(`[Seed] Starting seed for user ${userId}...`)
const libraryRepository = dataSource.getRepository(LibraryItemEntity)
const sampleItems = [
{
userId,
title: 'Building Scalable NestJS Applications',
slug: 'building-scalable-nestjs-applications',
originalUrl: 'https://docs.nestjs.com/techniques/database',
author: 'NestJS Team',
description:
'Learn how to build scalable, maintainable applications with NestJS framework',
state: LibraryItemState.SUCCEEDED,
contentReader: ContentReaderType.WEB,
folder: 'inbox',
itemType: 'ARTICLE',
wordCount: 2500,
siteName: 'NestJS Docs',
readingProgressTopPercent: 0,
readingProgressBottomPercent: 0,
savedAt: new Date(Date.now() - 1000 * 60 * 60), // 1 hour ago
},
{
userId,
title: 'GraphQL Best Practices 2025',
slug: 'graphql-best-practices-2025',
originalUrl: 'https://graphql.org/learn/best-practices/',
author: 'GraphQL Foundation',
description:
'Modern best practices for designing and implementing GraphQL APIs',
state: LibraryItemState.SUCCEEDED,
contentReader: ContentReaderType.WEB,
folder: 'inbox',
itemType: 'ARTICLE',
wordCount: 3200,
siteName: 'GraphQL.org',
readingProgressTopPercent: 45,
readingProgressBottomPercent: 40,
savedAt: new Date(Date.now() - 1000 * 60 * 60 * 3), // 3 hours ago
},
{
userId,
title: 'Understanding React Server Components',
slug: 'understanding-react-server-components',
originalUrl:
'https://react.dev/reference/react/use-server',
author: 'React Team',
description:
'Deep dive into React Server Components and their impact on modern web applications',
state: LibraryItemState.SUCCEEDED,
contentReader: ContentReaderType.WEB,
folder: 'inbox',
itemType: 'ARTICLE',
wordCount: 4100,
siteName: 'React.dev',
readingProgressTopPercent: 100,
readingProgressBottomPercent: 100,
readAt: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago
savedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2), // 2 days ago
},
{
userId,
title: 'TypeScript 5.8 Release Notes',
slug: 'typescript-5-8-release-notes',
originalUrl: 'https://devblogs.microsoft.com/typescript/',
author: 'TypeScript Team',
description:
'New features and improvements in TypeScript 5.8 release',
state: LibraryItemState.SUCCEEDED,
contentReader: ContentReaderType.WEB,
folder: 'archive',
itemType: 'ARTICLE',
wordCount: 1800,
siteName: 'TypeScript Blog',
readingProgressTopPercent: 100,
readingProgressBottomPercent: 100,
readAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), // 3 days ago
savedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5), // 5 days ago
},
{
userId,
title: 'PostgreSQL Performance Tuning Guide',
slug: 'postgresql-performance-tuning-guide',
originalUrl: 'https://www.postgresql.org/docs/current/performance-tips.html',
author: 'PostgreSQL Community',
description:
'Comprehensive guide to optimizing PostgreSQL database performance',
state: LibraryItemState.SUCCEEDED,
contentReader: ContentReaderType.WEB,
folder: 'inbox',
itemType: 'ARTICLE',
wordCount: 5400,
siteName: 'PostgreSQL Docs',
readingProgressTopPercent: 15,
readingProgressBottomPercent: 10,
savedAt: new Date(Date.now() - 1000 * 60 * 60 * 12), // 12 hours ago
},
]
const createdItems: LibraryItemEntity[] = []
for (const itemData of sampleItems) {
try {
console.log(`[Seed] Attempting to create: ${itemData.title}`)
const item = libraryRepository.create({
id: randomUUID(), // Manually generate UUID like E2E tests do
...itemData,
user: { id: userId } as any, // TypeORM relation requirement
readingProgressLastReadAnchor: itemData.readingProgressTopPercent > 0 ? 1 : 0,
readingProgressHighestReadAnchor: itemData.readingProgressTopPercent > 0 ? 1 : 0,
})
console.log(`[Seed] Created entity object, now saving...`)
const saved = await libraryRepository.save(item)
console.log(`[Seed] ✓ Created item: ${saved.title} (ID: ${saved.id})`)
createdItems.push(saved)
} catch (error: any) {
console.error(`[Seed] ✗ Failed to create item "${itemData.title}":`)
console.error(`[Seed] Error name: ${error?.name}`)
console.error(`[Seed] Error message: ${error?.message}`)
if (error?.detail) console.error(`[Seed] Error detail: ${error.detail}`)
if (error?.constraint) console.error(`[Seed] Constraint: ${error.constraint}`)
console.error(`[Seed] Full error:`, error)
}
}
console.log(`✅ [Seed] Completed: ${createdItems.length}/${sampleItems.length} items created for user ${userId}`)
return createdItems
}

View file

@ -0,0 +1,41 @@
import { Controller, Post, UseGuards, HttpCode } from '@nestjs/common'
import { DataSource } from 'typeorm'
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'
import { CurrentUser } from '../user/decorators/current-user.decorator'
import { User } from '../user/entities/user.entity'
import { seedLibraryItems } from '../database/seeds/library-items.seed'
@Controller('library')
export class LibraryController {
constructor(private readonly dataSource: DataSource) {}
/**
* Development endpoint to seed example library items for current user
* DELETE this endpoint before production deployment
*/
@Post('seed')
@UseGuards(JwtAuthGuard)
@HttpCode(200)
async seedLibrary(@CurrentUser() user: User) {
console.log(`[Controller] Seeding library for user: ${user.id}`)
try {
const items = await seedLibraryItems(this.dataSource, user.id)
return {
success: true,
message: `Seeded ${items.length} example library items`,
itemIds: items.map((item) => item.id),
items: items.map((item) => ({
id: item.id,
title: item.title,
folder: item.folder,
state: item.state,
})),
}
} catch (error) {
console.error('[Controller] Seed failed:', error)
throw error
}
}
}