Tanya b6a9765315
Some checks failed
CI / skip-ci-check (push) Successful in 1m27s
CI / skip-ci-check (pull_request) Successful in 1m27s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / lint-and-type-check (push) Successful in 2m4s
CI / python-lint (push) Successful in 1m53s
CI / test-backend (push) Successful in 2m37s
CI / build (push) Failing after 2m13s
CI / secret-scanning (push) Successful in 1m40s
CI / dependency-scan (push) Successful in 1m34s
CI / sast-scan (push) Successful in 2m42s
CI / workflow-summary (push) Successful in 1m26s
chore: Update project configuration and enhance code quality
This commit modifies the `.gitignore` file to exclude Python library directories while ensuring the viewer-frontend's `lib` directory is not ignored. It also updates the `package.json` to activate the virtual environment during backend tests, improving the testing process. Additionally, the CI workflow is enhanced to prevent duplicate runs for branches with open pull requests. Various components in the viewer frontend are updated to ensure consistent naming conventions and improve type safety. These changes contribute to a cleaner codebase and a more efficient development workflow.
2026-01-07 12:29:17 -05:00

246 lines
7.4 KiB
TypeScript

import { Suspense } from 'react';
import { prisma } from '@/lib/db';
import { HomePageContent } from './HomePageContent';
import { Photo } from '@prisma/client';
import { serializePhotos, serializePeople, serializeTags } from '@/lib/serialize';
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: {
id: true,
photo_id: true,
person_id: true,
location: true,
confidence: true,
quality_score: true,
is_primary_encoding: true,
detector_backend: true,
model_name: true,
face_confidence: true,
exif_orientation: true,
pose_mode: true,
yaw_angle: true,
pitch_angle: true,
roll_angle: true,
landmarks: true,
identified_by_user_id: true,
excluded: true,
Person: {
select: {
id: true,
first_name: true,
last_name: true,
middle_name: true,
maiden_name: true,
date_of_birth: true,
created_date: true,
},
},
// Exclude encoding field (Bytes) to avoid P2023 conversion errors
},
});
// 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 className="w-full px-4 py-8">
{error ? (
<div 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>
);
}