All files / uploads presigned-url.js

95.83% Statements 23/24
93.75% Branches 15/16
100% Functions 1/1
95.83% Lines 23/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 1141x 1x 1x   1x   1x 23x   23x 2x                       21x                     21x 20x     20x 8x                           12x 2x                     10x 10x     10x     10x                             10x     9x                         2x 2x                    
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
const { randomUUID } = require('crypto')
 
const s3Client = new S3Client({ region: 'eu-central-1' })
 
exports.handler = async (event) => {
  try {
    // Only allow POST requests
    if (event.httpMethod !== 'POST') {
      return {
        statusCode: 405,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type',
          'Access-Control-Allow-Methods': 'POST, OPTIONS'
        },
        body: JSON.stringify({ error: 'Method not allowed' })
      }
    }
 
    // Handle CORS preflight
    Iif (event.httpMethod === 'OPTIONS') {
      return {
        statusCode: 200,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type, Authorization',
          'Access-Control-Allow-Methods': 'POST, OPTIONS'
        }
      }
    }
 
    const body = JSON.parse(event.body || '{}')
    const { fileName, fileType, tenantId, entityId, itemId, fieldName } = body
 
    // Validate required parameters
    if (!fileName || !fileType || !tenantId || !entityId || !itemId || !fieldName) {
      return {
        statusCode: 400,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type'
        },
        body: JSON.stringify({
          error:
            'Missing required parameters: fileName, fileType, tenantId, entityId, itemId, fieldName'
        })
      }
    }
 
    // Validate file type (only images)
    if (!fileType.startsWith('image/')) {
      return {
        statusCode: 400,
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type'
        },
        body: JSON.stringify({ error: 'Only image files are allowed' })
      }
    }
 
    // Generate unique filename
    const fileExtension = fileName.split('.').pop()
    const uniqueFileName = `${randomUUID()}.${fileExtension}`
 
    // Create S3 key with organized folder structure
    const key = `${tenantId}/${entityId}/${itemId}/${fieldName}/${uniqueFileName}`
 
    // Create the PutObject command
    const command = new PutObjectCommand({
      Bucket: process.env.ATTACHMENTS_BUCKET_NAME,
      Key: key,
      ContentType: fileType,
      // Optional: Add metadata
      Metadata: {
        tenantId,
        entityId,
        itemId,
        fieldName,
        originalFileName: fileName
      }
    })
 
    // Generate presigned URL (expires in 15 minutes)
    const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 900 })
 
    // Return the presigned URL and the S3 key
    return {
      statusCode: 200,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'Content-Type'
      },
      body: JSON.stringify({
        uploadUrl: signedUrl,
        key: key,
        bucket: process.env.ATTACHMENTS_BUCKET_NAME
      })
    }
  } catch (error) {
    console.error('Error generating presigned URL:', error)
    return {
      statusCode: 500,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'Content-Type'
      },
      body: JSON.stringify({ error: 'Internal server error' })
    }
  }
}