fix event files
This commit is contained in:
parent
4fcb98ba57
commit
94b5e39c1a
3 changed files with 188 additions and 193 deletions
|
@ -744,10 +744,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
|
||||||
const formData = new FormData(e.currentTarget);
|
const formData = new FormData(e.currentTarget);
|
||||||
|
|
||||||
// Create updated event object
|
// Create updated event object
|
||||||
const updatedEvent: Event = {
|
const updatedEvent: Omit<Event, 'created' | 'updated'> = {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
created: event.created,
|
|
||||||
updated: event.updated,
|
|
||||||
event_name: formData.get("editEventName") as string,
|
event_name: formData.get("editEventName") as string,
|
||||||
event_description: formData.get("editEventDescription") as string,
|
event_description: formData.get("editEventDescription") as string,
|
||||||
event_code: formData.get("editEventCode") as string,
|
event_code: formData.get("editEventCode") as string,
|
||||||
|
@ -764,187 +762,135 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
|
||||||
await services.sendLog.send(
|
await services.sendLog.send(
|
||||||
"update",
|
"update",
|
||||||
"event",
|
"event",
|
||||||
`Updating event: ${updatedEvent.event_name} (${updatedEvent.id})`
|
`${event.id ? "Updating" : "Creating"} event: ${updatedEvent.event_name} (${event.id || "new"})`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process file changes
|
if (event.id) {
|
||||||
const uploadQueue = new UploadQueue();
|
// We're updating an existing event
|
||||||
const fileChanges: FileChanges = {
|
|
||||||
added: selectedFiles,
|
|
||||||
deleted: filesToDelete,
|
|
||||||
unchanged: event.files?.filter(file => !filesToDelete.has(file)) || []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle file deletions
|
// First, update the event data without touching files
|
||||||
if (fileChanges.deleted.size > 0) {
|
const { files, ...cleanPayload } = updatedEvent;
|
||||||
for (const fileId of fileChanges.deleted) {
|
await services.update.updateFields<Event>(
|
||||||
await services.fileManager.deleteFile("events", event.id, fileId);
|
Collections.EVENTS,
|
||||||
}
|
event.id,
|
||||||
}
|
cleanPayload
|
||||||
|
);
|
||||||
|
|
||||||
// Handle file uploads - only upload new files
|
// Handle file operations
|
||||||
if (fileChanges.added.size > 0) {
|
if (filesToDelete.size > 0 || selectedFiles.size > 0) {
|
||||||
const uploadErrors: string[] = [];
|
// Get the current event with its files
|
||||||
const fileManager = services.fileManager;
|
const currentEvent = await services.get.getOne<Event>(Collections.EVENTS, event.id);
|
||||||
|
let currentFiles = currentEvent?.files || [];
|
||||||
|
|
||||||
// Check for unsupported file types first
|
// 1. Remove files marked for deletion
|
||||||
const invalidFiles = Array.from(fileChanges.added.entries())
|
if (filesToDelete.size > 0) {
|
||||||
.map(([filename, file]) => {
|
console.log(`Removing ${filesToDelete.size} files from event ${event.id}`);
|
||||||
const validation = fileManager.validateFileType(file);
|
currentFiles = currentFiles.filter(file => !filesToDelete.has(file));
|
||||||
return { filename, file, validation };
|
|
||||||
})
|
|
||||||
.filter(item => !item.validation.valid);
|
|
||||||
|
|
||||||
if (invalidFiles.length > 0) {
|
// Update the files field first to remove deleted files
|
||||||
const errorMessage = `The following files cannot be uploaded:\n${invalidFiles.map(item => `${item.filename}: ${item.validation.reason}`).join('\n')}`;
|
await services.update.updateFields<Event>(
|
||||||
toast.error(errorMessage);
|
Collections.EVENTS,
|
||||||
throw new Error(errorMessage);
|
event.id,
|
||||||
}
|
{ files: currentFiles }
|
||||||
|
);
|
||||||
for (const [filename, file] of fileChanges.added.entries()) {
|
|
||||||
await uploadQueue.add(async () => {
|
|
||||||
try {
|
|
||||||
// Validate file size before compression
|
|
||||||
const maxSize = 200 * 1024 * 1024; // 200MB
|
|
||||||
if (file.size > maxSize) {
|
|
||||||
throw new Error(`File ${filename} exceeds 200MB limit`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compress image if it's an image file
|
|
||||||
const compressedFile = file.type.startsWith('image/')
|
|
||||||
? await services.fileManager.compressImageIfNeeded(file, 10) // 10MB limit for images
|
|
||||||
: file;
|
|
||||||
|
|
||||||
console.log(`Uploading file ${filename}:`, {
|
|
||||||
originalSize: file.size,
|
|
||||||
compressedSize: compressedFile.size,
|
|
||||||
type: file.type
|
|
||||||
});
|
|
||||||
|
|
||||||
// Upload the file to PocketBase
|
|
||||||
const uploadedFile = await services.fileManager.uploadFile(
|
|
||||||
"events",
|
|
||||||
event.id,
|
|
||||||
"files", // Use the correct field name from the schema
|
|
||||||
compressedFile
|
|
||||||
);
|
|
||||||
|
|
||||||
if (uploadedFile && uploadedFile.files) {
|
|
||||||
// Get the filename from the uploaded file
|
|
||||||
const uploadedFilename = uploadedFile.files[uploadedFile.files.length - 1];
|
|
||||||
fileChanges.unchanged.push(uploadedFilename);
|
|
||||||
console.log(`Successfully uploaded ${filename} as ${uploadedFilename}`);
|
|
||||||
} else {
|
|
||||||
console.warn(`File uploaded but no filename returned:`, uploadedFile);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
||||||
console.error('File upload failed:', {
|
|
||||||
filename,
|
|
||||||
error: errorMsg,
|
|
||||||
fileInfo: {
|
|
||||||
size: file.size,
|
|
||||||
type: file.type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
uploadErrors.push(`${filename}: ${errorMsg}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any uploads failed, show error and stop
|
|
||||||
if (uploadErrors.length > 0) {
|
|
||||||
const errorMessage = `Failed to upload files:\n${uploadErrors.join('\n')}`;
|
|
||||||
toast.error(errorMessage);
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the event with the new file list
|
|
||||||
updatedEvent.files = fileChanges.unchanged;
|
|
||||||
|
|
||||||
// Save the event
|
|
||||||
let savedEvent;
|
|
||||||
try {
|
|
||||||
if (event.id) {
|
|
||||||
// Update existing event
|
|
||||||
savedEvent = await services.update.updateFields<Event>(
|
|
||||||
Collections.EVENTS,
|
|
||||||
event.id,
|
|
||||||
updatedEvent
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clear cache to ensure fresh data
|
|
||||||
const dataSync = DataSyncService.getInstance();
|
|
||||||
await dataSync.clearCache();
|
|
||||||
|
|
||||||
// Update the window object with the latest event data
|
|
||||||
const eventDataId = `event_${event.id}`;
|
|
||||||
if ((window as any)[eventDataId]) {
|
|
||||||
(window as any)[eventDataId] = savedEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Event updated successfully!");
|
// 2. Add new files one by one to preserve existing ones
|
||||||
} else {
|
if (selectedFiles.size > 0) {
|
||||||
// Create new event
|
console.log(`Adding ${selectedFiles.size} new files to event ${event.id}`);
|
||||||
savedEvent = await services.update.create<Event>(
|
|
||||||
Collections.EVENTS,
|
|
||||||
updatedEvent
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log success
|
// Convert Map to array of File objects
|
||||||
await services.sendLog.send(
|
const newFiles = Array.from(selectedFiles.values());
|
||||||
"success",
|
|
||||||
"event_create",
|
|
||||||
`Successfully created event: ${savedEvent.event_name}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show success toast
|
// Use FileManager to upload each file individually
|
||||||
toast.success(`Event "${savedEvent.event_name}" created successfully!`);
|
for (const file of newFiles) {
|
||||||
|
// Use the FileManager to upload this file
|
||||||
|
await services.fileManager.uploadFile(
|
||||||
|
Collections.EVENTS,
|
||||||
|
event.id,
|
||||||
|
'files',
|
||||||
|
file,
|
||||||
|
true // Set append mode to true to preserve existing files
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset form state
|
// Get the final updated event with all changes
|
||||||
setEvent({
|
const savedEvent = await services.get.getOne<Event>(Collections.EVENTS, event.id);
|
||||||
id: "",
|
|
||||||
created: "",
|
|
||||||
updated: "",
|
|
||||||
event_name: "",
|
|
||||||
event_description: "",
|
|
||||||
event_code: "",
|
|
||||||
location: "",
|
|
||||||
files: [],
|
|
||||||
points_to_reward: 0,
|
|
||||||
start_date: "",
|
|
||||||
end_date: "",
|
|
||||||
published: false,
|
|
||||||
has_food: false
|
|
||||||
});
|
|
||||||
setSelectedFiles(new Map());
|
|
||||||
setFilesToDelete(new Set());
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
|
|
||||||
// Close modal
|
// Clear cache to ensure fresh data
|
||||||
const modal = document.getElementById("editEventModal") as HTMLDialogElement;
|
const dataSync = DataSyncService.getInstance();
|
||||||
if (modal) modal.close();
|
await dataSync.clearCache();
|
||||||
|
|
||||||
// Refresh events list
|
// Update the window object with the latest event data
|
||||||
if (window.fetchEvents) {
|
const eventDataId = `event_${event.id}`;
|
||||||
window.fetchEvents();
|
if ((window as any)[eventDataId]) {
|
||||||
|
(window as any)[eventDataId] = savedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger callback
|
toast.success("Event updated successfully!");
|
||||||
if (onEventSaved) {
|
|
||||||
onEventSaved();
|
// Call the onEventSaved callback if provided
|
||||||
|
if (onEventSaved) onEventSaved();
|
||||||
|
|
||||||
|
// Close the modal
|
||||||
|
handleModalClose();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We're creating a new event
|
||||||
|
|
||||||
|
// Create the event first without files
|
||||||
|
const { files, ...cleanPayload } = updatedEvent;
|
||||||
|
const newEvent = await services.update.create<Event>(
|
||||||
|
Collections.EVENTS,
|
||||||
|
cleanPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then upload files if any
|
||||||
|
if (selectedFiles.size > 0 && newEvent?.id) {
|
||||||
|
console.log(`Adding ${selectedFiles.size} files to new event ${newEvent.id}`);
|
||||||
|
|
||||||
|
// Convert Map to array of File objects
|
||||||
|
const newFiles = Array.from(selectedFiles.values());
|
||||||
|
|
||||||
|
// Upload files to the new event
|
||||||
|
for (const file of newFiles) {
|
||||||
|
await services.fileManager.uploadFile(
|
||||||
|
Collections.EVENTS,
|
||||||
|
newEvent.id,
|
||||||
|
'files',
|
||||||
|
file,
|
||||||
|
true // Set append mode to true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to save event:", error);
|
// Clear cache to ensure fresh data
|
||||||
toast.error(`Failed to ${event.id ? "update" : "create"} event: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
const dataSync = DataSyncService.getInstance();
|
||||||
|
await dataSync.clearCache();
|
||||||
|
|
||||||
|
toast.success("Event created successfully!");
|
||||||
|
|
||||||
|
// Call the onEventSaved callback if provided
|
||||||
|
if (onEventSaved) onEventSaved();
|
||||||
|
|
||||||
|
// Close the modal
|
||||||
|
handleModalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh events list if available
|
||||||
|
if (window.fetchEvents) window.fetchEvents();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save event:", error);
|
||||||
|
toast.error(`Failed to save event: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
window.hideLoading?.();
|
window.hideLoading?.();
|
||||||
}
|
}
|
||||||
}, [event, selectedFiles, filesToDelete, services, onEventSaved, isSubmitting]);
|
}, [event, selectedFiles, filesToDelete, services, onEventSaved, isSubmitting, handleModalClose]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog id="editEventModal" className="modal">
|
<dialog id="editEventModal" className="modal">
|
||||||
|
|
|
@ -43,6 +43,7 @@ export class FileManager {
|
||||||
* @param recordId The ID of the record to attach the file to
|
* @param recordId The ID of the record to attach the file to
|
||||||
* @param field The field name for the file
|
* @param field The field name for the file
|
||||||
* @param file The file to upload
|
* @param file The file to upload
|
||||||
|
* @param append Whether to append the file to existing files (default: false)
|
||||||
* @returns The updated record
|
* @returns The updated record
|
||||||
*/
|
*/
|
||||||
public async uploadFile<T = any>(
|
public async uploadFile<T = any>(
|
||||||
|
@ -50,6 +51,7 @@ export class FileManager {
|
||||||
recordId: string,
|
recordId: string,
|
||||||
field: string,
|
field: string,
|
||||||
file: File,
|
file: File,
|
||||||
|
append: boolean = false
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
if (!this.auth.isAuthenticated()) {
|
if (!this.auth.isAuthenticated()) {
|
||||||
throw new Error("User must be authenticated to upload files");
|
throw new Error("User must be authenticated to upload files");
|
||||||
|
@ -82,9 +84,16 @@ export class FileManager {
|
||||||
extension: fileExtension,
|
extension: fileExtension,
|
||||||
collection: collectionName,
|
collection: collectionName,
|
||||||
recordId: recordId,
|
recordId: recordId,
|
||||||
field: field
|
field: field,
|
||||||
|
append: append
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create FormData for the upload
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Use the + prefix for the field name if append is true
|
||||||
|
const fieldName = append ? `${field}+` : field;
|
||||||
|
|
||||||
// Get existing record to preserve existing files
|
// Get existing record to preserve existing files
|
||||||
let existingRecord: any = null;
|
let existingRecord: any = null;
|
||||||
let existingFiles: string[] = [];
|
let existingFiles: string[] = [];
|
||||||
|
@ -98,9 +107,13 @@ export class FileManager {
|
||||||
console.warn('Could not fetch existing record:', error);
|
console.warn('Could not fetch existing record:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the file already exists in the record
|
// Check if the file already exists
|
||||||
let fileToUpload = file;
|
const fileExists = existingFiles.some(existingFile =>
|
||||||
if (recordId && existingFiles.includes(file.name)) {
|
existingFile.toLowerCase() === file.name.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fileExists) {
|
||||||
|
console.warn(`File with name ${file.name} already exists. Renaming to avoid conflicts.`);
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
const nameParts = file.name.split('.');
|
const nameParts = file.name.split('.');
|
||||||
const extension = nameParts.pop();
|
const extension = nameParts.pop();
|
||||||
|
@ -108,31 +121,10 @@ export class FileManager {
|
||||||
const newFileName = `${baseName}_${timestamp}.${extension}`;
|
const newFileName = `${baseName}_${timestamp}.${extension}`;
|
||||||
|
|
||||||
// Create a new file with the modified name
|
// Create a new file with the modified name
|
||||||
fileToUpload = new File([file], newFileName, { type: file.type });
|
const newFile = new File([file], newFileName, { type: file.type });
|
||||||
|
formData.append(fieldName, newFile);
|
||||||
console.log(`Renamed duplicate file from ${file.name} to ${newFileName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create FormData and append file
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
// For events collection, use the 'files' field from the schema
|
|
||||||
if (collectionName === 'events') {
|
|
||||||
// Only append the new file, don't re-upload existing files
|
|
||||||
formData.append('files', fileToUpload);
|
|
||||||
|
|
||||||
// If this is an update operation and we have existing files, we need to tell PocketBase to keep them
|
|
||||||
if (recordId && existingFiles.length > 0) {
|
|
||||||
formData.append('files@', ''); // This tells PocketBase to keep existing files
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// For other collections, use the provided field name
|
formData.append(fieldName, file);
|
||||||
formData.append(field, fileToUpload);
|
|
||||||
|
|
||||||
// If this is an update operation and we have existing files, we need to tell PocketBase to keep them
|
|
||||||
if (recordId && existingFiles.length > 0) {
|
|
||||||
formData.append(`${field}@`, ''); // This tells PocketBase to keep existing files
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -140,9 +132,9 @@ export class FileManager {
|
||||||
console.log('Upload successful:', {
|
console.log('Upload successful:', {
|
||||||
result,
|
result,
|
||||||
fileInfo: {
|
fileInfo: {
|
||||||
name: fileToUpload.name,
|
name: file.name,
|
||||||
size: fileToUpload.size,
|
size: file.size,
|
||||||
type: fileToUpload.type
|
type: file.type
|
||||||
},
|
},
|
||||||
collection: collectionName,
|
collection: collectionName,
|
||||||
recordId: recordId
|
recordId: recordId
|
||||||
|
|
|
@ -256,4 +256,61 @@ export class Update {
|
||||||
this.auth.setUpdating(false);
|
this.auth.setUpdating(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a record with file appends
|
||||||
|
* This method properly handles appending files to existing records
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record to update
|
||||||
|
* @param data Regular fields to update
|
||||||
|
* @param files Object mapping field names to arrays of files to append
|
||||||
|
* @returns The updated record
|
||||||
|
*/
|
||||||
|
public async updateWithFileAppends<T = any>(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
data: Record<string, any> = {},
|
||||||
|
files: Record<string, File[]> = {}
|
||||||
|
): Promise<T> {
|
||||||
|
if (!this.auth.isAuthenticated()) {
|
||||||
|
throw new Error("User must be authenticated to update records");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.auth.setUpdating(true);
|
||||||
|
const pb = this.auth.getPocketBase();
|
||||||
|
|
||||||
|
// Convert regular data fields
|
||||||
|
const convertedData = convertLocalToUTC(data);
|
||||||
|
|
||||||
|
// Create FormData for the update
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Add regular fields
|
||||||
|
Object.entries(convertedData).forEach(([key, value]) => {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
formData.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add files with the + prefix to append them
|
||||||
|
Object.entries(files).forEach(([fieldName, fieldFiles]) => {
|
||||||
|
fieldFiles.forEach(file => {
|
||||||
|
formData.append(`${fieldName}+`, file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Perform the update
|
||||||
|
const result = await pb
|
||||||
|
.collection(collectionName)
|
||||||
|
.update<T>(recordId, formData);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to update with file appends in ${collectionName}:`, err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
this.auth.setUpdating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue