/** * Serialization utilities for converting Prisma objects to plain JavaScript objects * that can be safely passed from Server Components to Client Components in Next.js. * * Handles: * - Decimal objects -> numbers * - Date objects -> ISO strings * - Nested structures (photos, faces, people, tags) */ type Decimal = { toNumber(): number; toString(): string; }; /** * Checks if a value is a Prisma Decimal object */ function isDecimal(value: any): value is Decimal { return ( value !== null && typeof value === 'object' && typeof value.toNumber === 'function' && typeof value.toString === 'function' ); } /** * Converts a Decimal to a number, handling null/undefined */ function decimalToNumber(value: any): number | null { if (value === null || value === undefined) { return null; } if (isDecimal(value)) { return value.toNumber(); } if (typeof value === 'number') { return value; } // Fallback: try to parse as number const parsed = Number(value); return isNaN(parsed) ? null : parsed; } /** * Serializes a Date object to an ISO string */ function serializeDate(value: any): string | null { if (value === null || value === undefined) { return null; } if (value instanceof Date) { return value.toISOString(); } if (typeof value === 'string') { return value; } return null; } /** * Serializes a Person object */ function serializePerson(person: any): any { if (!person) { return null; } return { id: person.id, first_name: person.first_name, last_name: person.last_name, middle_name: person.middle_name ?? null, maiden_name: person.maiden_name ?? null, date_of_birth: serializeDate(person.date_of_birth), created_date: serializeDate(person.created_date), }; } /** * Serializes a Face object, converting Decimal fields to numbers */ function serializeFace(face: any): any { if (!face) { return null; } const serialized: any = { id: face.id, photo_id: face.photo_id, person_id: face.person_id ?? null, location: face.location, confidence: decimalToNumber(face.confidence) ?? 0, quality_score: decimalToNumber(face.quality_score) ?? 0, is_primary_encoding: face.is_primary_encoding ?? false, detector_backend: face.detector_backend, model_name: face.model_name, face_confidence: decimalToNumber(face.face_confidence) ?? 0, exif_orientation: face.exif_orientation ?? null, pose_mode: face.pose_mode, yaw_angle: decimalToNumber(face.yaw_angle), pitch_angle: decimalToNumber(face.pitch_angle), roll_angle: decimalToNumber(face.roll_angle), landmarks: face.landmarks ?? null, identified_by_user_id: face.identified_by_user_id ?? null, excluded: face.excluded ?? false, }; // Handle nested Person object (if present) if (face.Person) { serialized.Person = serializePerson(face.Person); } else if (face.person) { serialized.person = serializePerson(face.person); } return serialized; } /** * Serializes a Tag object */ function serializeTag(tag: any): any { if (!tag) { return null; } return { id: tag.id, tagName: tag.tag_name || tag.tagName, created_date: serializeDate(tag.created_date), }; } /** * Serializes a PhotoTagLinkage object */ function serializePhotoTagLinkage(linkage: any): any { if (!linkage) { return null; } const serialized: any = { linkage_id: linkage.linkage_id ?? linkage.id, photo_id: linkage.photo_id, tag_id: linkage.tag_id, linkage_type: linkage.linkage_type ?? 0, created_date: serializeDate(linkage.created_date), }; // Handle nested Tag object (if present) if (linkage.Tag || linkage.tag) { serialized.tag = serializeTag(linkage.Tag || linkage.tag); // Also keep Tag for backward compatibility serialized.Tag = serialized.tag; } if (linkage.Tag || linkage.tag) { // Also keep Tag for backward compatibility serialized.Tag = serialized.tag; } return serialized; } /** * Serializes a single Photo object with all nested structures */ export function serializePhoto(photo: any): any { if (!photo) { return null; } const serialized: any = { id: photo.id, path: photo.path, filename: photo.filename, date_added: serializeDate(photo.date_added), date_taken: serializeDate(photo.date_taken), processed: photo.processed ?? false, media_type: photo.media_type ?? null, }; // Handle Face array (can be named Face or faces) if (photo.Face && Array.isArray(photo.Face)) { serialized.Face = photo.Face.map((face: any) => serializeFace(face)); } else if (photo.faces && Array.isArray(photo.faces)) { serialized.faces = photo.faces.map((face: any) => serializeFace(face)); } // Handle PhotoTagLinkage array (can be named PhotoTagLinkage or photoTags) if (photo.PhotoTagLinkage && Array.isArray(photo.PhotoTagLinkage)) { serialized.PhotoTagLinkage = photo.PhotoTagLinkage.map((linkage: any) => serializePhotoTagLinkage(linkage) ); } else if (photo.photoTags && Array.isArray(photo.photoTags)) { serialized.photoTags = photo.photoTags.map((linkage: any) => serializePhotoTagLinkage(linkage) ); } return serialized; } /** * Serializes an array of Photo objects */ export function serializePhotos(photos: any[]): any[] { if (!Array.isArray(photos)) { return []; } return photos.map((photo) => serializePhoto(photo)); } /** * Serializes an array of Person objects */ export function serializePeople(people: any[]): any[] { if (!Array.isArray(people)) { return []; } return people.map((person) => serializePerson(person)); } /** * Serializes an array of Tag objects */ export function serializeTags(tags: any[]): any[] { if (!Array.isArray(tags)) { return []; } return tags.map((tag) => serializeTag(tag)); }