Files
Vault/scripts/check-db-integrity.ts
T
twotalesanimation 81ad7e4ea9 Initial commit
2026-06-11 10:46:09 +02:00

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);
});