mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Use attachments from old CollectionFS database structure, when not yet migrated to Meteor-Files/ostrio-files, without needing to migrate database structure.
Thanks to xet7 !
This commit is contained in:
parent
dda013844c
commit
a8de2f224f
6 changed files with 698 additions and 2 deletions
150
server/migrations/migrateAttachments.js
Normal file
150
server/migrations/migrateAttachments.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { getOldAttachmentData, getOldAttachmentDataBuffer } from '/models/lib/attachmentBackwardCompatibility';
|
||||
|
||||
/**
|
||||
* Migration script to convert old CollectionFS attachments to new Meteor-Files structure
|
||||
* This script can be run to migrate all old attachments to the new format
|
||||
*/
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Migrate a single attachment from old to new structure
|
||||
* @param {string} attachmentId - The old attachment ID
|
||||
* @returns {Object} - Migration result
|
||||
*/
|
||||
migrateAttachment(attachmentId) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get old attachment data
|
||||
const oldAttachment = getOldAttachmentData(attachmentId);
|
||||
if (!oldAttachment) {
|
||||
return { success: false, error: 'Old attachment not found' };
|
||||
}
|
||||
|
||||
// Check if already migrated
|
||||
const existingAttachment = ReactiveCache.getAttachment(attachmentId);
|
||||
if (existingAttachment) {
|
||||
return { success: true, message: 'Already migrated', attachmentId };
|
||||
}
|
||||
|
||||
// Get file data from GridFS
|
||||
const fileData = getOldAttachmentDataBuffer(attachmentId);
|
||||
if (!fileData) {
|
||||
return { success: false, error: 'Could not read file data from GridFS' };
|
||||
}
|
||||
|
||||
// Create new attachment using Meteor-Files
|
||||
const fileObj = new File([fileData], oldAttachment.name, {
|
||||
type: oldAttachment.type
|
||||
});
|
||||
|
||||
const uploader = Attachments.insert({
|
||||
file: fileObj,
|
||||
meta: oldAttachment.meta,
|
||||
isBase64: false,
|
||||
transport: 'http'
|
||||
});
|
||||
|
||||
if (uploader) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Migration successful',
|
||||
attachmentId,
|
||||
newAttachmentId: uploader._id
|
||||
};
|
||||
} else {
|
||||
return { success: false, error: 'Failed to create new attachment' };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error migrating attachment:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Migrate all attachments for a specific card
|
||||
* @param {string} cardId - The card ID
|
||||
* @returns {Object} - Migration results
|
||||
*/
|
||||
migrateCardAttachments(cardId) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
const results = {
|
||||
success: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
try {
|
||||
// Get all old attachments for this card
|
||||
const oldAttachments = ReactiveCache.getAttachments({ 'meta.cardId': cardId });
|
||||
|
||||
for (const attachment of oldAttachments) {
|
||||
const result = Meteor.call('migrateAttachment', attachment._id);
|
||||
if (result.success) {
|
||||
results.success++;
|
||||
} else {
|
||||
results.failed++;
|
||||
results.errors.push({
|
||||
attachmentId: attachment._id,
|
||||
error: result.error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error migrating card attachments:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get migration status for attachments
|
||||
* @param {string} cardId - The card ID (optional)
|
||||
* @returns {Object} - Migration status
|
||||
*/
|
||||
getAttachmentMigrationStatus(cardId) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
try {
|
||||
const selector = cardId ? { 'meta.cardId': cardId } : {};
|
||||
const allAttachments = ReactiveCache.getAttachments(selector);
|
||||
|
||||
const status = {
|
||||
total: allAttachments.length,
|
||||
newStructure: 0,
|
||||
oldStructure: 0,
|
||||
mixed: false
|
||||
};
|
||||
|
||||
for (const attachment of allAttachments) {
|
||||
if (attachment.meta && attachment.meta.source === 'legacy') {
|
||||
status.oldStructure++;
|
||||
} else {
|
||||
status.newStructure++;
|
||||
}
|
||||
}
|
||||
|
||||
status.mixed = status.oldStructure > 0 && status.newStructure > 0;
|
||||
|
||||
return status;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting migration status:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
72
server/routes/legacyAttachments.js
Normal file
72
server/routes/legacyAttachments.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { WebApp } from 'meteor/webapp';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { getAttachmentWithBackwardCompatibility, getOldAttachmentStream } from '/models/lib/attachmentBackwardCompatibility';
|
||||
|
||||
// Ensure this file is loaded
|
||||
console.log('Legacy attachments route loaded');
|
||||
|
||||
/**
|
||||
* Legacy attachment download route for CollectionFS compatibility
|
||||
* Handles downloads from old CollectionFS structure
|
||||
*/
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// Handle legacy attachment downloads
|
||||
WebApp.connectHandlers.use('/cfs/files/attachments', (req, res, next) => {
|
||||
const attachmentId = req.url.split('/').pop();
|
||||
|
||||
if (!attachmentId) {
|
||||
res.writeHead(404);
|
||||
res.end('Attachment not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to get attachment with backward compatibility
|
||||
const attachment = getAttachmentWithBackwardCompatibility(attachmentId);
|
||||
|
||||
if (!attachment) {
|
||||
res.writeHead(404);
|
||||
res.end('Attachment not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
const board = ReactiveCache.getBoard(attachment.meta.boardId);
|
||||
if (!board) {
|
||||
res.writeHead(404);
|
||||
res.end('Board not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user has permission to download
|
||||
const userId = Meteor.userId();
|
||||
if (!board.isPublic() && (!userId || !board.hasMember(userId))) {
|
||||
res.writeHead(403);
|
||||
res.end('Access denied');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set appropriate headers
|
||||
res.setHeader('Content-Type', attachment.type || 'application/octet-stream');
|
||||
res.setHeader('Content-Length', attachment.size || 0);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`);
|
||||
|
||||
// Get GridFS stream for legacy attachment
|
||||
const fileStream = getOldAttachmentStream(attachmentId);
|
||||
if (fileStream) {
|
||||
res.writeHead(200);
|
||||
fileStream.pipe(res);
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('File not found in GridFS');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving legacy attachment:', error);
|
||||
res.writeHead(500);
|
||||
res.end('Internal server error');
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue