Files
punimtag/viewer-frontend/app/page.tsx
T
ilia 66e5ec3a45 Slim gallery face payloads for home SSR and search API.
Grid only needs location + person names; drop landmarks/pose/detector fields.
2026-08-04 21:34:23 -04:00

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