218 lines
6.8 KiB
TypeScript
218 lines
6.8 KiB
TypeScript
import { Suspense } from 'react';
|
|
import { prisma } from '@/lib/db';
|
|
import { HomePageContent } from './HomePageContent';
|
|
import { serializePhotos, serializePeople, serializeTags } from '@/lib/serialize';
|
|
import { gridFaceSelect } from '@/lib/grid-face-select';
|
|
|
|
// Force dynamic rendering to prevent database queries during build
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
async function getAllPeople() {
|
|
try {
|
|
return await prisma.person.findMany({
|
|
select: {
|
|
id: true,
|
|
first_name: true,
|
|
last_name: true,
|
|
middle_name: true,
|
|
maiden_name: true,
|
|
date_of_birth: true,
|
|
created_date: true,
|
|
},
|
|
orderBy: [
|
|
{ first_name: 'asc' },
|
|
{ last_name: 'asc' },
|
|
],
|
|
});
|
|
} catch (error: any) {
|
|
// Handle corrupted data errors (P2023)
|
|
if (error?.code === 'P2023' || error?.message?.includes('Conversion failed')) {
|
|
console.warn('Corrupted person data detected, attempting fallback query');
|
|
try {
|
|
// Try with minimal fields first
|
|
return await prisma.person.findMany({
|
|
select: {
|
|
id: true,
|
|
first_name: true,
|
|
last_name: true,
|
|
// Exclude potentially corrupted optional fields
|
|
},
|
|
orderBy: [
|
|
{ first_name: 'asc' },
|
|
{ last_name: 'asc' },
|
|
],
|
|
});
|
|
} catch (fallbackError: any) {
|
|
console.error('Fallback person query also failed:', fallbackError);
|
|
// Return empty array as last resort to prevent page crash
|
|
return [];
|
|
}
|
|
}
|
|
// Re-throw if it's a different error
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function getAllTags() {
|
|
try {
|
|
return await prisma.tag.findMany({
|
|
select: {
|
|
id: true,
|
|
tag_name: true,
|
|
created_date: true,
|
|
},
|
|
orderBy: { tag_name: 'asc' },
|
|
});
|
|
} catch (error: any) {
|
|
// Handle corrupted data errors (P2023)
|
|
if (error?.code === 'P2023' || error?.message?.includes('Conversion failed')) {
|
|
console.warn('Corrupted tag data detected, attempting fallback query');
|
|
try {
|
|
// Try with minimal fields
|
|
return await prisma.tag.findMany({
|
|
select: {
|
|
id: true,
|
|
tag_name: true,
|
|
// Exclude potentially corrupted date field
|
|
},
|
|
orderBy: { tag_name: 'asc' },
|
|
});
|
|
} catch (fallbackError: any) {
|
|
console.error('Fallback tag query also failed:', fallbackError);
|
|
// Return empty array as last resort to prevent page crash
|
|
return [];
|
|
}
|
|
}
|
|
// Re-throw if it's a different error
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export default async function HomePage() {
|
|
// Fetch photos from database
|
|
// Note: Make sure DATABASE_URL is set in .env file
|
|
let photos: any[] = []; // Using any to handle select-based query return type
|
|
let error: string | null = null;
|
|
|
|
try {
|
|
// Fetch first page of photos (30 photos) for initial load
|
|
// Infinite scroll will load more as user scrolls
|
|
// Try to load with date fields first, fallback if corrupted data exists
|
|
let photosBase;
|
|
try {
|
|
// Use raw query to read dates as strings and convert manually to avoid Prisma conversion issues
|
|
const photosRaw = await prisma.$queryRaw<Array<{
|
|
id: number;
|
|
path: string;
|
|
filename: string;
|
|
date_added: string;
|
|
date_taken: string | null;
|
|
processed: boolean;
|
|
media_type: string | null;
|
|
}>>`
|
|
SELECT
|
|
id,
|
|
path,
|
|
filename,
|
|
date_added,
|
|
date_taken,
|
|
processed,
|
|
media_type
|
|
FROM photos
|
|
WHERE processed = true
|
|
ORDER BY date_taken DESC, id DESC
|
|
LIMIT 30
|
|
`;
|
|
|
|
photosBase = photosRaw.map(photo => ({
|
|
id: photo.id,
|
|
path: photo.path,
|
|
filename: photo.filename,
|
|
date_added: new Date(photo.date_added),
|
|
date_taken: photo.date_taken ? new Date(photo.date_taken) : null,
|
|
processed: photo.processed,
|
|
media_type: photo.media_type,
|
|
}));
|
|
} catch (dateError: any) {
|
|
// If date fields are corrupted, load without them and use fallback values
|
|
// Check for P2023 error code or various date conversion error messages
|
|
const isDateError = dateError?.code === 'P2023' ||
|
|
dateError?.message?.includes('Conversion failed') ||
|
|
dateError?.message?.includes('Inconsistent column data') ||
|
|
dateError?.message?.includes('Could not convert value');
|
|
|
|
if (isDateError) {
|
|
console.warn('Corrupted date data detected, loading photos without date fields');
|
|
photosBase = await prisma.photo.findMany({
|
|
where: { processed: true },
|
|
select: {
|
|
id: true,
|
|
path: true,
|
|
filename: true,
|
|
processed: true,
|
|
media_type: true,
|
|
// Exclude date fields due to corruption
|
|
},
|
|
orderBy: { id: 'desc' },
|
|
take: 30,
|
|
});
|
|
// Add fallback date values
|
|
photosBase = photosBase.map(photo => ({
|
|
...photo,
|
|
date_added: new Date(),
|
|
date_taken: null,
|
|
}));
|
|
} else {
|
|
throw dateError;
|
|
}
|
|
}
|
|
|
|
// If base query works, load faces separately
|
|
const photoIds = photosBase.map(p => p.id);
|
|
const faces = await prisma.face.findMany({
|
|
where: { photo_id: { in: photoIds } },
|
|
select: gridFaceSelect,
|
|
});
|
|
|
|
// Combine the data manually
|
|
photos = photosBase.map(photo => ({
|
|
...photo,
|
|
Face: faces.filter(face => face.photo_id === photo.id),
|
|
})) as any;
|
|
} catch (err) {
|
|
error = err instanceof Error ? err.message : 'Failed to load photos';
|
|
console.error('Error loading photos:', err);
|
|
}
|
|
|
|
// Fetch people and tags for search
|
|
const [people, tags] = await Promise.all([
|
|
getAllPeople(),
|
|
getAllTags(),
|
|
]);
|
|
|
|
return (
|
|
<main id="main-content" className="w-full px-4 pt-3 pb-8">
|
|
|
|
{error ? (
|
|
<div role="alert" className="rounded-lg bg-red-50 p-4 text-red-800 dark:bg-red-900/20 dark:text-red-200">
|
|
<p className="font-semibold">Error loading photos</p>
|
|
<p className="text-sm">{error}</p>
|
|
<p className="mt-2 text-xs">
|
|
Make sure DATABASE_URL is configured in your .env file
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<Suspense fallback={
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="text-center">
|
|
<p className="text-gray-600 dark:text-gray-400">Loading...</p>
|
|
</div>
|
|
</div>
|
|
}>
|
|
<HomePageContent initialPhotos={serializePhotos(photos)} people={serializePeople(people)} tags={serializeTags(tags)} />
|
|
</Suspense>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|