| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 | 
							- import { Meteor } from 'meteor/meteor';
 
- import { WebApp } from 'meteor/webapp';
 
- import { ReactiveCache } from '/imports/reactiveCache';
 
- import { Attachments, fileStoreStrategyFactory } from '/models/attachments';
 
- import { moveToStorage } from '/models/lib/fileStoreStrategy';
 
- import { STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS, STORAGE_NAME_S3 } from '/models/lib/fileStoreStrategy';
 
- import AttachmentStorageSettings from '/models/attachmentStorageSettings';
 
- import fs from 'fs';
 
- import path from 'path';
 
- import { ObjectID } from 'bson';
 
- // Attachment API HTTP routes
 
- if (Meteor.isServer) {
 
-   // Helper function to authenticate API requests
 
-   function authenticateApiRequest(req) {
 
-     const authHeader = req.headers.authorization;
 
-     if (!authHeader || !authHeader.startsWith('Bearer ')) {
 
-       throw new Meteor.Error('unauthorized', 'Missing or invalid authorization header');
 
-     }
 
-     const token = authHeader.substring(7);
 
-     // Here you would validate the token and get the user ID
 
-     // For now, we'll use a simple approach - in production, you'd want proper JWT validation
 
-     const userId = token; // This should be replaced with proper token validation
 
-     
 
-     if (!userId) {
 
-       throw new Meteor.Error('unauthorized', 'Invalid token');
 
-     }
 
-     return userId;
 
-   }
 
-   // Helper function to send JSON response
 
-   function sendJsonResponse(res, statusCode, data) {
 
-     res.writeHead(statusCode, { 'Content-Type': 'application/json' });
 
-     res.end(JSON.stringify(data));
 
-   }
 
-   // Helper function to send error response
 
-   function sendErrorResponse(res, statusCode, message) {
 
-     sendJsonResponse(res, statusCode, { success: false, error: message });
 
-   }
 
-   // Upload attachment endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/upload', (req, res, next) => {
 
-     if (req.method !== 'POST') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       
 
-       let body = '';
 
-       req.on('data', chunk => {
 
-         body += chunk.toString();
 
-       });
 
-       req.on('end', () => {
 
-         try {
 
-           const data = JSON.parse(body);
 
-           const { boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend } = data;
 
-           // Validate parameters
 
-           if (!boardId || !swimlaneId || !listId || !cardId || !fileData || !fileName) {
 
-             return sendErrorResponse(res, 400, 'Missing required parameters');
 
-           }
 
-           // Check if user has permission to modify the card
 
-           const card = ReactiveCache.getCard(cardId);
 
-           if (!card) {
 
-             return sendErrorResponse(res, 404, 'Card not found');
 
-           }
 
-           const board = ReactiveCache.getBoard(boardId);
 
-           if (!board) {
 
-             return sendErrorResponse(res, 404, 'Board not found');
 
-           }
 
-           // Check permissions
 
-           if (!board.isBoardMember(userId)) {
 
-             return sendErrorResponse(res, 403, 'You do not have permission to modify this card');
 
-           }
 
-           // Check if board allows attachments
 
-           if (!board.allowsAttachments) {
 
-             return sendErrorResponse(res, 403, 'Attachments are not allowed on this board');
 
-           }
 
-           // Get default storage backend if not specified
 
-           let targetStorage = storageBackend;
 
-           if (!targetStorage) {
 
-             try {
 
-               const settings = AttachmentStorageSettings.findOne({});
 
-               targetStorage = settings ? settings.getDefaultStorage() : STORAGE_NAME_FILESYSTEM;
 
-             } catch (error) {
 
-               targetStorage = STORAGE_NAME_FILESYSTEM;
 
-             }
 
-           }
 
-           // Validate storage backend
 
-           if (![STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS, STORAGE_NAME_S3].includes(targetStorage)) {
 
-             return sendErrorResponse(res, 400, 'Invalid storage backend');
 
-           }
 
-           // Create file object from base64 data
 
-           const fileBuffer = Buffer.from(fileData, 'base64');
 
-           const file = new File([fileBuffer], fileName, { type: fileType || 'application/octet-stream' });
 
-           // Create attachment metadata
 
-           const fileId = new ObjectID().toString();
 
-           const meta = {
 
-             boardId: boardId,
 
-             swimlaneId: swimlaneId,
 
-             listId: listId,
 
-             cardId: cardId,
 
-             fileId: fileId,
 
-             source: 'api',
 
-             storageBackend: targetStorage
 
-           };
 
-           // Create attachment
 
-           const uploader = Attachments.insert({
 
-             file: file,
 
-             meta: meta,
 
-             isBase64: false,
 
-             transport: 'http'
 
-           });
 
-           if (uploader) {
 
-             // Move to target storage if not filesystem
 
-             if (targetStorage !== STORAGE_NAME_FILESYSTEM) {
 
-               Meteor.defer(() => {
 
-                 try {
 
-                   moveToStorage(uploader, targetStorage, fileStoreStrategyFactory);
 
-                 } catch (error) {
 
-                   console.error('Error moving attachment to target storage:', error);
 
-                 }
 
-               });
 
-             }
 
-             sendJsonResponse(res, 200, {
 
-               success: true,
 
-               attachmentId: uploader._id,
 
-               fileName: fileName,
 
-               fileSize: fileBuffer.length,
 
-               storageBackend: targetStorage,
 
-               message: 'Attachment uploaded successfully'
 
-             });
 
-           } else {
 
-             sendErrorResponse(res, 500, 'Failed to upload attachment');
 
-           }
 
-         } catch (error) {
 
-           console.error('API attachment upload error:', error);
 
-           sendErrorResponse(res, 500, error.message);
 
-         }
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // Download attachment endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/download/([^/]+)', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       const attachmentId = req.params[0];
 
-       // Get attachment
 
-       const attachment = ReactiveCache.getAttachment(attachmentId);
 
-       if (!attachment) {
 
-         return sendErrorResponse(res, 404, 'Attachment not found');
 
-       }
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(attachment.meta.boardId);
 
-       if (!board || !board.isBoardMember(userId)) {
 
-         return sendErrorResponse(res, 403, 'You do not have permission to access this attachment');
 
-       }
 
-       // Get file strategy
 
-       const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original');
 
-       const readStream = strategy.getReadStream();
 
-       if (!readStream) {
 
-         return sendErrorResponse(res, 404, 'File not found in storage');
 
-       }
 
-       // Read file data
 
-       const chunks = [];
 
-       readStream.on('data', (chunk) => {
 
-         chunks.push(chunk);
 
-       });
 
-       readStream.on('end', () => {
 
-         const fileBuffer = Buffer.concat(chunks);
 
-         const base64Data = fileBuffer.toString('base64');
 
-         
 
-         sendJsonResponse(res, 200, {
 
-           success: true,
 
-           attachmentId: attachmentId,
 
-           fileName: attachment.name,
 
-           fileSize: attachment.size,
 
-           fileType: attachment.type,
 
-           base64Data: base64Data,
 
-           storageBackend: strategy.getStorageName()
 
-         });
 
-       });
 
-       readStream.on('error', (error) => {
 
-         console.error('Download error:', error);
 
-         sendErrorResponse(res, 500, error.message);
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // List attachments endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/list/([^/]+)/([^/]+)/([^/]+)/([^/]+)', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       const boardId = req.params[0];
 
-       const swimlaneId = req.params[1];
 
-       const listId = req.params[2];
 
-       const cardId = req.params[3];
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(boardId);
 
-       if (!board || !board.isBoardMember(userId)) {
 
-         return sendErrorResponse(res, 403, 'You do not have permission to access this board');
 
-       }
 
-       let query = { 'meta.boardId': boardId };
 
-       if (swimlaneId && swimlaneId !== 'null') {
 
-         query['meta.swimlaneId'] = swimlaneId;
 
-       }
 
-       if (listId && listId !== 'null') {
 
-         query['meta.listId'] = listId;
 
-       }
 
-       if (cardId && cardId !== 'null') {
 
-         query['meta.cardId'] = cardId;
 
-       }
 
-       const attachments = ReactiveCache.getAttachments(query);
 
-       
 
-       const attachmentList = attachments.map(attachment => {
 
-         const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original');
 
-         return {
 
-           attachmentId: attachment._id,
 
-           fileName: attachment.name,
 
-           fileSize: attachment.size,
 
-           fileType: attachment.type,
 
-           storageBackend: strategy.getStorageName(),
 
-           boardId: attachment.meta.boardId,
 
-           swimlaneId: attachment.meta.swimlaneId,
 
-           listId: attachment.meta.listId,
 
-           cardId: attachment.meta.cardId,
 
-           createdAt: attachment.uploadedAt,
 
-           isImage: attachment.isImage
 
-         };
 
-       });
 
-       sendJsonResponse(res, 200, {
 
-         success: true,
 
-         attachments: attachmentList,
 
-         count: attachmentList.length
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // Copy attachment endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/copy', (req, res, next) => {
 
-     if (req.method !== 'POST') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       
 
-       let body = '';
 
-       req.on('data', chunk => {
 
-         body += chunk.toString();
 
-       });
 
-       req.on('end', () => {
 
-         try {
 
-           const data = JSON.parse(body);
 
-           const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data;
 
-           // Get source attachment
 
-           const sourceAttachment = ReactiveCache.getAttachment(attachmentId);
 
-           if (!sourceAttachment) {
 
-             return sendErrorResponse(res, 404, 'Source attachment not found');
 
-           }
 
-           // Check source permissions
 
-           const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId);
 
-           if (!sourceBoard || !sourceBoard.isBoardMember(userId)) {
 
-             return sendErrorResponse(res, 403, 'You do not have permission to access the source attachment');
 
-           }
 
-           // Check target permissions
 
-           const targetBoard = ReactiveCache.getBoard(targetBoardId);
 
-           if (!targetBoard || !targetBoard.isBoardMember(userId)) {
 
-             return sendErrorResponse(res, 403, 'You do not have permission to modify the target card');
 
-           }
 
-           // Check if target board allows attachments
 
-           if (!targetBoard.allowsAttachments) {
 
-             return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board');
 
-           }
 
-           // Get source file strategy
 
-           const sourceStrategy = fileStoreStrategyFactory.getFileStrategy(sourceAttachment, 'original');
 
-           const readStream = sourceStrategy.getReadStream();
 
-           if (!readStream) {
 
-             return sendErrorResponse(res, 404, 'Source file not found in storage');
 
-           }
 
-           // Read source file data
 
-           const chunks = [];
 
-           readStream.on('data', (chunk) => {
 
-             chunks.push(chunk);
 
-           });
 
-           readStream.on('end', () => {
 
-             try {
 
-               const fileBuffer = Buffer.concat(chunks);
 
-               const file = new File([fileBuffer], sourceAttachment.name, { type: sourceAttachment.type });
 
-               // Create new attachment metadata
 
-               const fileId = new ObjectID().toString();
 
-               const meta = {
 
-                 boardId: targetBoardId,
 
-                 swimlaneId: targetSwimlaneId,
 
-                 listId: targetListId,
 
-                 cardId: targetCardId,
 
-                 fileId: fileId,
 
-                 source: 'api-copy',
 
-                 copyFrom: attachmentId,
 
-                 copyStorage: sourceStrategy.getStorageName()
 
-               };
 
-               // Create new attachment
 
-               const uploader = Attachments.insert({
 
-                 file: file,
 
-                 meta: meta,
 
-                 isBase64: false,
 
-                 transport: 'http'
 
-               });
 
-               if (uploader) {
 
-                 sendJsonResponse(res, 200, {
 
-                   success: true,
 
-                   sourceAttachmentId: attachmentId,
 
-                   newAttachmentId: uploader._id,
 
-                   fileName: sourceAttachment.name,
 
-                   fileSize: sourceAttachment.size,
 
-                   message: 'Attachment copied successfully'
 
-                 });
 
-               } else {
 
-                 sendErrorResponse(res, 500, 'Failed to copy attachment');
 
-               }
 
-             } catch (error) {
 
-               sendErrorResponse(res, 500, error.message);
 
-             }
 
-           });
 
-           readStream.on('error', (error) => {
 
-             sendErrorResponse(res, 500, error.message);
 
-           });
 
-         } catch (error) {
 
-           console.error('API attachment copy error:', error);
 
-           sendErrorResponse(res, 500, error.message);
 
-         }
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // Move attachment endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/move', (req, res, next) => {
 
-     if (req.method !== 'POST') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       
 
-       let body = '';
 
-       req.on('data', chunk => {
 
-         body += chunk.toString();
 
-       });
 
-       req.on('end', () => {
 
-         try {
 
-           const data = JSON.parse(body);
 
-           const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data;
 
-           // Get source attachment
 
-           const sourceAttachment = ReactiveCache.getAttachment(attachmentId);
 
-           if (!sourceAttachment) {
 
-             return sendErrorResponse(res, 404, 'Source attachment not found');
 
-           }
 
-           // Check source permissions
 
-           const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId);
 
-           if (!sourceBoard || !sourceBoard.isBoardMember(userId)) {
 
-             return sendErrorResponse(res, 403, 'You do not have permission to access the source attachment');
 
-           }
 
-           // Check target permissions
 
-           const targetBoard = ReactiveCache.getBoard(targetBoardId);
 
-           if (!targetBoard || !targetBoard.isBoardMember(userId)) {
 
-             return sendErrorResponse(res, 403, 'You do not have permission to modify the target card');
 
-           }
 
-           // Check if target board allows attachments
 
-           if (!targetBoard.allowsAttachments) {
 
-             return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board');
 
-           }
 
-           // Update attachment metadata
 
-           Attachments.update(attachmentId, {
 
-             $set: {
 
-               'meta.boardId': targetBoardId,
 
-               'meta.swimlaneId': targetSwimlaneId,
 
-               'meta.listId': targetListId,
 
-               'meta.cardId': targetCardId,
 
-               'meta.source': 'api-move',
 
-               'meta.movedAt': new Date()
 
-             }
 
-           });
 
-           sendJsonResponse(res, 200, {
 
-             success: true,
 
-             attachmentId: attachmentId,
 
-             fileName: sourceAttachment.name,
 
-             fileSize: sourceAttachment.size,
 
-             sourceBoardId: sourceAttachment.meta.boardId,
 
-             targetBoardId: targetBoardId,
 
-             message: 'Attachment moved successfully'
 
-           });
 
-         } catch (error) {
 
-           console.error('API attachment move error:', error);
 
-           sendErrorResponse(res, 500, error.message);
 
-         }
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // Delete attachment endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/delete/([^/]+)', (req, res, next) => {
 
-     if (req.method !== 'DELETE') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       const attachmentId = req.params[0];
 
-       // Get attachment
 
-       const attachment = ReactiveCache.getAttachment(attachmentId);
 
-       if (!attachment) {
 
-         return sendErrorResponse(res, 404, 'Attachment not found');
 
-       }
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(attachment.meta.boardId);
 
-       if (!board || !board.isBoardMember(userId)) {
 
-         return sendErrorResponse(res, 403, 'You do not have permission to delete this attachment');
 
-       }
 
-       // Delete attachment
 
-       Attachments.remove(attachmentId);
 
-       sendJsonResponse(res, 200, {
 
-         success: true,
 
-         attachmentId: attachmentId,
 
-         fileName: attachment.name,
 
-         message: 'Attachment deleted successfully'
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
-   // Get attachment info endpoint
 
-   WebApp.connectHandlers.use('/api/attachment/info/([^/]+)', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const userId = authenticateApiRequest(req);
 
-       const attachmentId = req.params[0];
 
-       // Get attachment
 
-       const attachment = ReactiveCache.getAttachment(attachmentId);
 
-       if (!attachment) {
 
-         return sendErrorResponse(res, 404, 'Attachment not found');
 
-       }
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(attachment.meta.boardId);
 
-       if (!board || !board.isBoardMember(userId)) {
 
-         return sendErrorResponse(res, 403, 'You do not have permission to access this attachment');
 
-       }
 
-       const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original');
 
-       
 
-       sendJsonResponse(res, 200, {
 
-         success: true,
 
-         attachmentId: attachment._id,
 
-         fileName: attachment.name,
 
-         fileSize: attachment.size,
 
-         fileType: attachment.type,
 
-         storageBackend: strategy.getStorageName(),
 
-         boardId: attachment.meta.boardId,
 
-         swimlaneId: attachment.meta.swimlaneId,
 
-         listId: attachment.meta.listId,
 
-         cardId: attachment.meta.cardId,
 
-         createdAt: attachment.uploadedAt,
 
-         isImage: attachment.isImage,
 
-         versions: Object.keys(attachment.versions).map(versionName => ({
 
-           versionName: versionName,
 
-           storage: attachment.versions[versionName].storage,
 
-           size: attachment.versions[versionName].size,
 
-           type: attachment.versions[versionName].type
 
-         }))
 
-       });
 
-     } catch (error) {
 
-       sendErrorResponse(res, 401, error.message);
 
-     }
 
-   });
 
- }
 
 
  |