All files / entities link.js

100% Statements 32/32
92.85% Branches 13/14
100% Functions 1/1
100% Lines 32/32

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   1x         1x 10x 10x 10x 10x   10x 1x               9x             8x 1x             7x   7x 1x             6x 1x               5x             5x 1x             4x 4x                 4x             4x   4x           1x 1x               1x      
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
const { DynamoDBDocumentClient, GetCommand, PutCommand } = require('@aws-sdk/lib-dynamodb')
const { requirePermission } = require('../utils/requirePermission')
 
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
 
const CORS_HEADERS = {
  'Content-Type': 'application/json',
  'Access-Control-Allow-Origin': '*'
}
 
const linkEntityHandler = async (event) => {
  try {
    const entityId = event.pathParameters?.entityId
    const flowId = event.requestContext?.authorizer?.claims?.['custom:flowId']
    const userId = event.requestContext?.authorizer?.claims?.sub
 
    if (!entityId) {
      return {
        statusCode: 400,
        headers: CORS_HEADERS,
        body: JSON.stringify({ error: 'Missing entity id' })
      }
    }
 
    // Fetch source entity
    const entityResult = await docClient.send(
      new GetCommand({
        TableName: process.env.ENTITIES_TABLE_NAME,
        Key: { id: entityId }
      })
    )
 
    if (!entityResult.Item) {
      return {
        statusCode: 404,
        headers: CORS_HEADERS,
        body: JSON.stringify({ error: 'Entity not found' })
      }
    }
 
    const entity = entityResult.Item
 
    if (entity.visibility !== 'public') {
      return {
        statusCode: 403,
        headers: CORS_HEADERS,
        body: JSON.stringify({ error: 'Only public entities can be linked' })
      }
    }
 
    if (entity.flowId === flowId) {
      return {
        statusCode: 400,
        headers: CORS_HEADERS,
        body: JSON.stringify({ error: 'Cannot link your own entity' })
      }
    }
 
    // Check for duplicate link
    const existingLink = await docClient.send(
      new GetCommand({
        TableName: process.env.ENTITY_LINKS_TABLE_NAME,
        Key: { flowId, entityId }
      })
    )
 
    if (existingLink.Item) {
      return {
        statusCode: 409,
        headers: CORS_HEADERS,
        body: JSON.stringify({ error: 'Entity already linked to this flow' })
      }
    }
 
    const now = new Date().toISOString()
    const link = {
      flowId,
      entityId,
      sourceFlowId: entity.flowId,
      linkedAt: now,
      linkedBy: userId,
      archivedAt: entity.status === 'archived' ? entity.archivedAt || now : null
    }
 
    await docClient.send(
      new PutCommand({
        TableName: process.env.ENTITY_LINKS_TABLE_NAME,
        Item: link
      })
    )
 
    console.log('[link] Entity linked:', { flowId, entityId })
 
    return {
      statusCode: 201,
      headers: CORS_HEADERS,
      body: JSON.stringify(link)
    }
  } catch (error) {
    console.error('[link] Exception:', error.message, error.stack)
    return {
      statusCode: 500,
      headers: CORS_HEADERS,
      body: JSON.stringify({ error: 'Failed to link entity' })
    }
  }
}
 
exports.handler = requirePermission(linkEntityHandler, {
  permission: 'entity:create'
})