mirror of
https://github.com/mito-systems/ranger-app.git
synced 2026-04-30 02:24:05 +00:00
196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
// src/services/imageHashService.ts
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import crypto from 'crypto';
|
|
import { promises as fsPromises } from 'fs';
|
|
|
|
// Define the path to the JSON file that will store our hashes
|
|
// In a real production app, this would be replaced with a database
|
|
// For Vercel, we'll use the tmp directory which is writable
|
|
const HASH_FILE_PATH = process.env.NODE_ENV === 'production'
|
|
? path.join('/tmp', 'image-hashes.json') // Use /tmp for Vercel
|
|
: path.join(process.cwd(), 'image-hashes.json');
|
|
|
|
// Interface for storing image hash data
|
|
interface ImageHashData {
|
|
[hash: string]: {
|
|
timestamp: string;
|
|
description?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initialize the hash file if it doesn't exist
|
|
*/
|
|
async function initializeHashFile(): Promise<void> {
|
|
try {
|
|
try {
|
|
await fsPromises.access(HASH_FILE_PATH);
|
|
console.log('Hash file exists at:', HASH_FILE_PATH);
|
|
} catch (error) {
|
|
// File doesn't exist, create an empty one
|
|
console.log('Creating hash file at:', HASH_FILE_PATH);
|
|
|
|
// Create directory if it doesn't exist (for /tmp path)
|
|
const dir = path.dirname(HASH_FILE_PATH);
|
|
try {
|
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
} catch (mkdirError) {
|
|
// Ignore directory exists error
|
|
console.log('Note: Directory already exists or cannot be created');
|
|
}
|
|
|
|
await fsPromises.writeFile(HASH_FILE_PATH, JSON.stringify({}));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing hash file:', error);
|
|
// Don't throw - in Vercel we'll continue without hash checking
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read all stored image hashes
|
|
*/
|
|
export async function readImageHashes(): Promise<ImageHashData> {
|
|
await initializeHashFile();
|
|
|
|
try {
|
|
const data = await fsPromises.readFile(HASH_FILE_PATH, 'utf8');
|
|
return JSON.parse(data) as ImageHashData;
|
|
} catch (error) {
|
|
console.error('Error reading image hashes file:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a SHA-256 hash for an image buffer
|
|
*/
|
|
export function generateImageHash(imageBuffer: Buffer): string {
|
|
if (!imageBuffer || !(imageBuffer instanceof Buffer)) {
|
|
throw new Error('Invalid image buffer provided');
|
|
}
|
|
|
|
if (imageBuffer.length === 0) {
|
|
throw new Error('Empty image buffer');
|
|
}
|
|
|
|
return crypto.createHash('sha256').update(imageBuffer).digest('hex');
|
|
}
|
|
|
|
/**
|
|
* Check if an image hash already exists
|
|
*/
|
|
export async function isImageHashDuplicate(hash: string): Promise<boolean> {
|
|
const hashes = await readImageHashes();
|
|
return !!hashes[hash];
|
|
}
|
|
|
|
/**
|
|
* Store a new image hash
|
|
*/
|
|
export async function storeImageHash(hash: string, description?: string): Promise<void> {
|
|
const hashes = await readImageHashes();
|
|
|
|
hashes[hash] = {
|
|
timestamp: new Date().toISOString(),
|
|
description
|
|
};
|
|
|
|
try {
|
|
await fsPromises.writeFile(HASH_FILE_PATH, JSON.stringify(hashes, null, 2));
|
|
} catch (error) {
|
|
console.error('Error writing image hash:', error);
|
|
throw new Error('Failed to store image hash');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if an image is a duplicate and store if not
|
|
* Returns true if the image is a duplicate, false otherwise
|
|
*/
|
|
export async function checkAndStoreImageHash(
|
|
imageBuffer: Buffer,
|
|
description?: string
|
|
): Promise<boolean> {
|
|
// In Vercel serverless functions, file operations may fail due to permissions
|
|
// We'll still calculate the hash but handle failures gracefully
|
|
|
|
try {
|
|
// Validate buffer
|
|
if (!imageBuffer || !(imageBuffer instanceof Buffer)) {
|
|
console.error('Invalid image buffer provided to checkAndStoreImageHash');
|
|
throw new Error('Invalid image buffer provided');
|
|
}
|
|
|
|
if (imageBuffer.length === 0) {
|
|
console.error('Empty image buffer provided to checkAndStoreImageHash');
|
|
throw new Error('Empty image buffer');
|
|
}
|
|
|
|
const hash = generateImageHash(imageBuffer);
|
|
console.log(`Generated image hash: ${hash.substring(0, 8)}...`);
|
|
|
|
// Read the current hashes - log but don't throw on error
|
|
try {
|
|
const hashes = await readImageHashes();
|
|
const isDuplicate = !!hashes[hash];
|
|
|
|
// Log before storing
|
|
if (isDuplicate) {
|
|
console.log(`Duplicate image detected with hash: ${hash.substring(0, 8)}...`);
|
|
} else {
|
|
console.log(`New image with hash: ${hash.substring(0, 8)}...`);
|
|
}
|
|
|
|
// If not a duplicate or if we're updating with a description, update the hash file
|
|
if (!isDuplicate || description) {
|
|
hashes[hash] = {
|
|
timestamp: new Date().toISOString(),
|
|
description: description || hashes[hash]?.description
|
|
};
|
|
|
|
try {
|
|
await fsPromises.writeFile(HASH_FILE_PATH, JSON.stringify(hashes, null, 2));
|
|
console.log(`Successfully stored hash ${hash.substring(0, 8)}...`);
|
|
} catch (writeError) {
|
|
console.error('Warning: Could not write to hash file:', writeError.message);
|
|
// Continue without throwing - the hash check still worked
|
|
}
|
|
}
|
|
|
|
return isDuplicate;
|
|
} catch (readError) {
|
|
// If we can't read the hash file, assume it's not a duplicate
|
|
console.error('Warning: Could not read hash file:', readError.message);
|
|
|
|
// Try to create a new hash file with just this hash
|
|
try {
|
|
const singleHash = {
|
|
[hash]: {
|
|
timestamp: new Date().toISOString(),
|
|
description
|
|
}
|
|
};
|
|
await fsPromises.writeFile(HASH_FILE_PATH, JSON.stringify(singleHash, null, 2));
|
|
} catch (createError) {
|
|
console.error('Warning: Could not create new hash file:', createError.message);
|
|
}
|
|
|
|
return false; // Not a duplicate since we couldn't check
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in checkAndStoreImageHash:', error);
|
|
// Return false instead of throwing to avoid breaking the app flow
|
|
// This means we'll treat errors as "not a duplicate" and continue processing
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get details about a stored hash
|
|
*/
|
|
export async function getHashDetails(hash: string): Promise<any | null> {
|
|
const hashes = await readImageHashes();
|
|
return hashes[hash] || null;
|
|
} |