feat: Enhance API startup script and add file hash management for photos

This commit improves the `run_api_with_worker.sh` script by ensuring the virtual environment is created if it doesn't exist and dependencies are installed. It also adds a check to ensure the database schema is up to date. Additionally, new functionality has been introduced to calculate and store file hashes for uploaded photos, preventing duplicates. The database schema has been updated to include a `file_hash` column in the `photos` table, along with an index for efficient querying. The frontend has been updated to handle warnings for duplicate photos during the review process. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-11-24 13:16:41 -05:00
parent 93cb4eda5b
commit 661e812193
35 changed files with 593 additions and 5628 deletions
+1
View File
@@ -36,6 +36,7 @@ export interface ReviewResponse {
approved: number
rejected: number
errors: string[]
warnings?: string[] // Informational messages (e.g., duplicates)
}
export const pendingPhotosApi = {
+49 -9
View File
@@ -16,6 +16,12 @@ export default function PendingPhotos() {
const [submitting, setSubmitting] = useState(false)
const [statusFilter, setStatusFilter] = useState<string>('pending')
const [imageUrls, setImageUrls] = useState<Record<number, string>>({})
const [notification, setNotification] = useState<{
approved: number
rejected: number
warnings: string[]
errors: string[]
} | null>(null)
const imageUrlsRef = useRef<Record<number, string>>({})
const loadPendingPhotos = useCallback(async () => {
@@ -232,19 +238,20 @@ export default function PendingPhotos() {
decisions: decisionsList,
})
const message = [
`✅ Approved: ${response.approved}`,
`❌ Rejected: ${response.rejected}`,
response.errors.length > 0 ? `⚠️ Errors: ${response.errors.length}` : '',
]
.filter(Boolean)
.join('\n')
alert(message)
// Show custom notification instead of alert
setNotification({
approved: response.approved,
rejected: response.rejected,
warnings: response.warnings || [],
errors: response.errors,
})
if (response.errors.length > 0) {
console.error('Errors:', response.errors)
}
if (response.warnings && response.warnings.length > 0) {
console.info('Warnings:', response.warnings)
}
// Reload the list to show updated status
await loadPendingPhotos()
@@ -263,6 +270,39 @@ export default function PendingPhotos() {
return (
<div>
{/* Notification */}
{notification && (
<div className="mb-4 bg-white border border-gray-200 rounded-lg shadow-lg p-4">
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="text-green-600 text-lg">✅</span>
<span className="font-medium">Approved: {notification.approved}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-red-600 text-lg">✓</span>
<span className="font-medium">Rejected: {notification.rejected}</span>
</div>
{notification.warnings.length > 0 && (
<div className="text-xs text-gray-600 ml-7">
{notification.warnings.join(', ')}
</div>
)}
{notification.errors.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-yellow-600 text-lg">⚠️</span>
<span className="font-medium">Errors: {notification.errors.length}</span>
</div>
)}
</div>
<button
onClick={() => setNotification(null)}
className="mt-3 px-3 py-1.5 text-sm text-gray-600 bg-gray-50 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors"
>
Dismiss
</button>
</div>
)}
<div className="bg-white rounded-lg shadow p-6">
<h1 className="text-2xl font-bold text-gray-900 mb-6">Manage User Uploaded Photos</h1>