All files / entities list.js

96.15% Statements 25/26
50% Branches 4/8
100% Functions 3/3
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 961x 1x 1x   1x 1x     11x   11x   11x                         11x     11x 11x     11x                       10x                       10x 10x 10x     10x 10x 10x       10x                         1x 1x                     1x      
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
const { DynamoDBDocumentClient, QueryCommand } = require('@aws-sdk/lib-dynamodb')
const { requirePermission } = require('../utils/requirePermission')
 
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
 
async function getEntitiesHandler(event) {
  try {
    // Extract tenantId from JWT claims
    const tenantId = event.requestContext?.authorizer?.claims?.['custom:tenantId']
 
    Iif (!tenantId) {
      return {
        statusCode: 401,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ error: 'Missing tenant context' })
      }
    }
 
    // Pagination support - Note: For simplicity, we load all and paginate in memory
    // For large datasets, consider using DynamoDB pagination on each query separately
    const limit = event.queryStringParameters?.limit
      ? parseInt(event.queryStringParameters.limit)
      : 30
    const nextToken = event.queryStringParameters?.nextToken
    const offset = nextToken ? parseInt(Buffer.from(nextToken, 'base64').toString()) : 0
 
    // Query tenant's private entities
    const tenantEntities = await docClient.send(
      new QueryCommand({
        TableName: process.env.TABLE_NAME,
        IndexName: 'tenantIndex',
        KeyConditionExpression: 'tenantId = :tenantId',
        ExpressionAttributeValues: {
          ':tenantId': tenantId
        }
      })
    )
 
    // Query public entities
    const publicEntities = await docClient.send(
      new QueryCommand({
        TableName: process.env.TABLE_NAME,
        IndexName: 'publicIndex',
        KeyConditionExpression: 'isPublic = :isPublic',
        ExpressionAttributeValues: {
          ':isPublic': 'true'
        }
      })
    )
 
    // Merge and deduplicate (tenant entities take precedence)
    const tenantEntityIds = new Set(tenantEntities.Items.map((e) => e.id))
    const publicEntitiesFiltered = publicEntities.Items.filter((e) => !tenantEntityIds.has(e.id))
    const allEntities = [...tenantEntities.Items, ...publicEntitiesFiltered]
 
    // Apply pagination in memory
    const paginatedEntities = allEntities.slice(offset, offset + limit)
    const hasMore = offset + limit < allEntities.length
    const responseNextToken = hasMore
      ? Buffer.from(String(offset + limit)).toString('base64')
      : null
 
    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        items: paginatedEntities,
        nextToken: responseNextToken,
        hasMore
      })
    }
  } catch (error) {
    console.error('Error fetching entities:', error)
    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({ error: 'Failed to fetch entities' })
    }
  }
}
 
exports.handler = requirePermission(getEntitiesHandler, {
  permission: 'entity:read'
})