import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function validateDatabaseIntegrity() { console.log('šŸ” Running comprehensive database integrity check...'); try { // 1. Check Video table integrity console.log('\nšŸ“¹ Checking Video table...'); const videos = await prisma.video.findMany({ include: { playlist: true, uploader: true, _count: { select: { comments: true, likes: true, progress: true, unlocks: true, watchSegments: true, videoCourses: true } } } }); console.log(`āœ… Found ${videos.length} videos`); // 2. Check for videos with missing playlists const videosWithMissingPlaylists = videos.filter(v => !v.playlist); if (videosWithMissingPlaylists.length > 0) { console.log(`āš ļø Found ${videosWithMissingPlaylists.length} videos with missing playlists:`); videosWithMissingPlaylists.forEach(v => { console.log(` - Video ID: ${v.id}, Title: "${v.title}", Playlist ID: ${v.playlistId}`); }); } // 3. Check for playlists with no videos console.log('\nšŸ“‹ Checking Playlist table...'); const playlists = await prisma.playlist.findMany({ include: { videos: true, course: true, _count: { select: { videos: true } } } }); console.log(`āœ… Found ${playlists.length} playlists`); const emptyPlaylists = playlists.filter(p => p.videos.length === 0); if (emptyPlaylists.length > 0) { console.log(`āš ļø Found ${emptyPlaylists.length} empty playlists:`); emptyPlaylists.forEach(p => { console.log(` - Playlist ID: ${p.id}, Title: "${p.title}", Course: ${p.course?.title || 'Unknown'}`); }); } // 4. Check VideoProgress integrity console.log('\nšŸ“Š Checking VideoProgress table...'); const progressCount = await prisma.videoProgress.count(); console.log(`āœ… Found ${progressCount} progress records`); // Check for progress records with invalid video references const invalidProgress = await prisma.$queryRaw` SELECT vp.id, vp."videoId", vp."userId" FROM "VideoProgress" vp LEFT JOIN "Video" v ON vp."videoId" = v.id WHERE v.id IS NULL ` as Array<{id: string, videoId: string, userId: string}>; if (invalidProgress.length > 0) { console.log(`āŒ Found ${invalidProgress.length} progress records with invalid video references:`); invalidProgress.forEach(p => { console.log(` - Progress ID: ${p.id}, Video ID: ${p.videoId}, User ID: ${p.userId}`); }); } // 5. Check VideoWatchSegment integrity console.log('\nā±ļø Checking VideoWatchSegment table...'); const segmentsCount = await prisma.videoWatchSegment.count(); console.log(`āœ… Found ${segmentsCount} watch segments`); // Check for segments with invalid video references const invalidSegments = await prisma.$queryRaw` SELECT vws.id, vws."videoId", vws."userId" FROM "VideoWatchSegment" vws LEFT JOIN "Video" v ON vws."videoId" = v.id WHERE v.id IS NULL ` as Array<{id: string, videoId: string, userId: string}>; if (invalidSegments.length > 0) { console.log(`āŒ Found ${invalidSegments.length} watch segments with invalid video references:`); invalidSegments.forEach(s => { console.log(` - Segment ID: ${s.id}, Video ID: ${s.videoId}, User ID: ${s.userId}`); }); } // 6. Check for duplicate video indexes within playlists console.log('\nšŸ”„ Checking for duplicate video indexes...'); for (const playlist of playlists) { if (playlist.videos.length > 0) { const indexes = playlist.videos.map(v => v.index); const uniqueIndexes = [...new Set(indexes)]; if (indexes.length !== uniqueIndexes.length) { console.log(`āš ļø Playlist "${playlist.title}" has duplicate video indexes:`); const duplicates = indexes.filter((item, index) => indexes.indexOf(item) !== index); console.log(` - Duplicate indexes: ${duplicates.join(', ')}`); } } } // 7. Check Enrollment integrity console.log('\nšŸ“š Checking Enrollment table...'); const enrollments = await prisma.enrollment.findMany({ include: { user: true, course: true } }); console.log(`āœ… Found ${enrollments.length} enrollments`); const enrollmentsWithMissingUser = enrollments.filter(e => !e.user); const enrollmentsWithMissingCourse = enrollments.filter(e => !e.course); if (enrollmentsWithMissingUser.length > 0) { console.log(`āŒ Found ${enrollmentsWithMissingUser.length} enrollments with missing users`); } if (enrollmentsWithMissingCourse.length > 0) { console.log(`āŒ Found ${enrollmentsWithMissingCourse.length} enrollments with missing courses`); } // 8. Summary console.log('\nšŸ“‹ Database Integrity Summary:'); console.log(` Videos: ${videos.length}`); console.log(` Playlists: ${playlists.length} (${emptyPlaylists.length} empty)`); console.log(` Progress Records: ${progressCount}`); console.log(` Watch Segments: ${segmentsCount}`); console.log(` Enrollments: ${enrollments.length}`); const hasIssues = videosWithMissingPlaylists.length > 0 || invalidProgress.length > 0 || invalidSegments.length > 0 || enrollmentsWithMissingUser.length > 0 || enrollmentsWithMissingCourse.length > 0; if (!hasIssues) { console.log('✨ All integrity checks passed! Database appears healthy.'); } else { console.log('āš ļø Some integrity issues found. See details above.'); } } catch (error) { console.error('āŒ Error during integrity check:', error); } finally { await prisma.$disconnect(); } } // Run the integrity check validateDatabaseIntegrity() .then(() => { console.log('šŸ”§ Database integrity check completed'); process.exit(0); }) .catch((error) => { console.error('šŸ’„ Integrity check failed:', error); process.exit(1); });