|
@@ -0,0 +1,553 @@
|
|
|
+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);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|