feat: Add cleanup functionality for pending photos and database management

This commit introduces new API endpoints for cleaning up files and records related to pending photos. The frontend has been updated to include buttons for admins to trigger cleanup operations, allowing for the deletion of files from shared space and records from the pending_photos table. Additionally, the README has been updated with instructions for granting DELETE permissions on auth database tables, and a script has been added to automate this process. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-11-24 13:57:27 -05:00
parent 661e812193
commit 100d39c556
6 changed files with 434 additions and 18 deletions
+29
View File
@@ -39,6 +39,13 @@ export interface ReviewResponse {
warnings?: string[] // Informational messages (e.g., duplicates)
}
export interface CleanupResponse {
deleted_files: number
deleted_records: number
errors: string[]
warnings?: string[] // Informational messages (e.g., files already deleted)
}
export const pendingPhotosApi = {
listPendingPhotos: async (statusFilter?: string): Promise<PendingPhotosListResponse> => {
const { data } = await apiClient.get<PendingPhotosListResponse>(
@@ -73,5 +80,27 @@ export const pendingPhotosApi = {
)
return data
},
cleanupFiles: async (statusFilter?: string): Promise<CleanupResponse> => {
const { data } = await apiClient.post<CleanupResponse>(
'/api/v1/pending-photos/cleanup-files',
{},
{
params: statusFilter ? { status_filter: statusFilter } : undefined,
}
)
return data
},
cleanupDatabase: async (statusFilter?: string): Promise<CleanupResponse> => {
const { data } = await apiClient.post<CleanupResponse>(
'/api/v1/pending-photos/cleanup-database',
{},
{
params: statusFilter ? { status_filter: statusFilter } : undefined,
}
)
return data
},
}
+101
View File
@@ -3,10 +3,13 @@ import {
pendingPhotosApi,
PendingPhotoResponse,
ReviewDecision,
CleanupResponse,
} from '../api/pendingPhotos'
import { apiClient } from '../api/client'
import { useAuth } from '../context/AuthContext'
export default function PendingPhotos() {
const { isAdmin } = useAuth()
const [pendingPhotos, setPendingPhotos] = useState<PendingPhotoResponse[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@@ -268,6 +271,86 @@ export default function PendingPhotos() {
}
}
const handleCleanupFiles = async (statusFilter?: string) => {
const confirmMessage = statusFilter
? `Delete files from shared space for ${statusFilter} photos? This cannot be undone.`
: 'Delete files from shared space for all approved/rejected photos? This cannot be undone.'
if (!confirm(confirmMessage)) {
return
}
try {
const response: CleanupResponse = await pendingPhotosApi.cleanupFiles(statusFilter)
const message = [
`✅ Deleted ${response.deleted_files} file(s) from shared space`,
response.warnings && response.warnings.length > 0
? `ℹ️ ${response.warnings.length} file(s) were already deleted`
: '',
response.errors.length > 0 ? `⚠️ Errors: ${response.errors.length}` : '',
]
.filter(Boolean)
.join('\n')
alert(message)
if (response.warnings && response.warnings.length > 0) {
console.info('Cleanup warnings:', response.warnings)
}
if (response.errors.length > 0) {
console.error('Cleanup errors:', response.errors)
}
// Reload the list
await loadPendingPhotos()
} catch (err: any) {
const errorMessage =
err.response?.data?.detail || err.message || 'Failed to cleanup files'
alert(`Error: ${errorMessage}`)
console.error('Error cleaning up files:', err)
}
}
const handleCleanupDatabase = async (statusFilter?: string) => {
const confirmMessage = statusFilter
? `Delete all ${statusFilter} records from pending_photos table? This cannot be undone.`
: 'Delete ALL records from pending_photos table? This cannot be undone.'
if (!confirm(confirmMessage)) {
return
}
try {
const response: CleanupResponse = await pendingPhotosApi.cleanupDatabase(statusFilter)
const message = [
`✅ Deleted ${response.deleted_records} record(s) from database`,
response.warnings && response.warnings.length > 0
? `ℹ️ ${response.warnings.join(', ')}`
: '',
response.errors.length > 0
? `⚠️ Errors: ${response.errors.join('; ')}`
: '',
]
.filter(Boolean)
.join('\n')
alert(message)
if (response.warnings && response.warnings.length > 0) {
console.info('Cleanup warnings:', response.warnings)
}
if (response.errors.length > 0) {
console.error('Cleanup errors:', response.errors)
}
// Reload the list
await loadPendingPhotos()
} catch (err: any) {
const errorMessage =
err.response?.data?.detail || err.message || 'Failed to cleanup database'
alert(`Error: ${errorMessage}`)
console.error('Error cleaning up database:', err)
}
}
return (
<div>
{/* Notification */}
@@ -344,6 +427,24 @@ export default function PendingPhotos() {
<option value="rejected">Rejected</option>
</select>
</div>
{isAdmin && (
<div className="flex items-center gap-2">
<button
onClick={() => handleCleanupFiles()}
className="px-3 py-1.5 text-sm bg-orange-100 text-orange-700 rounded-md hover:bg-orange-200 font-medium"
title="Delete files from shared space for approved/rejected photos"
>
🗑️ Cleanup Files
</button>
<button
onClick={() => handleCleanupDatabase()}
className="px-3 py-1.5 text-sm bg-red-100 text-red-700 rounded-md hover:bg-red-200 font-medium"
title="Delete all records from pending_photos table"
>
🗑️ Clear Database
</button>
</div>
)}
<div className="flex items-center gap-2">
{pendingPhotos.filter((p) => p.status === 'pending').length > 0 && (
<>