171 lines
6.2 KiB
TypeScript
171 lines
6.2 KiB
TypeScript
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);
|
|
}); |