import db from '@mds-core/mds-db' ; import logger from '@mds-core/mds-logger' ; import aws from 'aws-sdk' ; import path from 'path' ; import sharp from 'sharp' ; import { uuid , UnsupportedTypeError , ValidationError } from '@mds-core/mds-utils' ; import { Attachment , AttachmentSummary , AuditAttachment , Recorded , UUID } from '@mds-core/mds-types' ; const multer = require ( 'multer' ) ; const { env } = process ; const supportedMimetypes = [ 'image/png' , 'image/jpeg' ] ; const thumbnailSize = Number ( env . ATTACHMENTS_THUMBNAIL_SIZE || 250 ) ; const s3Bucket = String ( env . ATTACHMENTS_BUCKET ) ; const s3BucketSubdir = String ( env . ATTACHMENTS_SUBDIR ) ; const s3Region = String ( env . ATTACHMENTS_REGION ) ; const s3ACL = String ( env . ATTACHMENTS_ACL ) ; const s3 = new aws . S3 ( ) ; const memoryStorage = multer . memoryStorage ( ) ; if ( env . ATTACHMENTS_BUCKET ) { aws . config . getCredentials ( async ( err ) => { if ( err ) { logger . error ( 'Error getting AWS credentials' , err . stack || err ) ; } else if ( aws . config . credentials ) { aws . config . update ( { secretAccessKey : aws . config . credentials . secretAccessKey , accessKeyId : aws . config . credentials . accessKeyId , region : s3Region } ) ; } } ) ; } export const multipartFormUpload = multer ( { storage : memoryStorage } ) . single ( 'file' ) ; export function attachmentSummary ( attachment ) { const thumbnailUrl = attachment . thumbnail_filename ? attachment . base_url + attachment . thumbnail_filename : '' ; return { attachment_id : attachment . attachment_id , attachment_url : attachment . base_url + attachment . attachment_filename , thumbnail_url : thumbnailUrl } ; } export function validateFile ( file ) { if ( ! file || file . buffer . byteLength === 0 ) { throw new ValidationError ( 'No attachment found' ) ; } else if ( path . extname ( file . originalname ) . replace ( '.' , '' ) === '' ) { throw new ValidationError ( `Missing file extension in filename ${ file . originalname }` ) ; } else if ( ! supportedMimetypes . includes ( file . mimetype ) ) { throw new UnsupportedTypeError ( `Unsupported mime type ${ file . mimetype }` ) ; } } async function writeAttachmentS3 ( file ) { const [ attachmentBuf , thumbnailBuf ] = await Promise . all ( [ sharp ( file . buffer ) . rotate ( ) . toBuffer ( ) , sharp ( file . buffer ) . rotate ( ) . resize ( thumbnailSize , thumbnailSize , { fit : sharp . fit . inside , withoutEnlargement : true } ) . toBuffer ( ) ] ) ; const attachmentId = uuid ( ) ; const ext = path . extname ( file . originalname ) . toLowerCase ( ) ; const attachmentFilename = attachmentId + ext ; const thumbnailFilename = `${ attachmentId }.thumbnail${ ext }` ; await Promise . all ( [ s3 . upload ( { Bucket : s3Bucket , Key : [ s3BucketSubdir , attachmentFilename ] . join ( '/' ) , Body : attachmentBuf , ACL : s3ACL } ) . promise ( ) , s3 . upload ( { Bucket : s3Bucket , Key : [ s3BucketSubdir , thumbnailFilename ] . join ( '/' ) , Body : thumbnailBuf , ACL : s3ACL } ) . promise ( ) ] ) ; return { attachment_filename : attachmentFilename , attachment_id : attachmentId , base_url : `https://${ s3Bucket }.s3-${ s3Region }.amazonaws.com/${ s3BucketSubdir }/` , mimetype : file . mimetype , thumbnail_filename : thumbnailFilename , thumbnail_mimetype : file . mimetype } ; } export async function writeAttachment ( file , auditTripId ) { const attachment = await writeAttachmentS3 ( file ) ; await db . writeAttachment ( attachment ) ; await db . writeAuditAttachment ( { attachment_id : attachment . attachment_id , audit_trip_id : auditTripId } as AuditAttachment ) ; return attachment ; } export async function readAttachments ( audit_trip_id ) { const result = await db . readAttachmentsForAudit ( audit_trip_id ) ; return result ; } export async function deleteAttachmentS3 ( attachment ) { const deletePromises = [ s3 . deleteObject ( { Bucket : s3Bucket , Key : s3Bucket . concat ( '/' , attachment . attachment_filename ) } ) . promise ( ) ] ; if ( attachment . thumbnail_filename ) { deletePromises . push ( s3 . deleteObject ( { Bucket : s3Bucket , Key : s3Bucket . concat ( '/' , attachment . thumbnail_filename ) } ) . promise ( ) ) ; } await Promise . all ( deletePromises ) ; } export async function deleteAuditAttachment ( auditTripId , attachmentId ) { try { await db . deleteAuditAttachment ( auditTripId , attachmentId ) ; const auditAttachments = await db . readAuditAttachments ( attachmentId ) ; if ( auditAttachments . length === 0 ) { const attachment = await db . deleteAttachment ( attachmentId ) ; if ( attachment ) { await deleteAttachmentS3 ( attachment ) ; } } } catch ( err ) { logger . error ( 'deleteAttachment error' , err . stack || err ) ; throw err ; } }