n8n/packages/nodes-base/nodes/Aws/DynamoDB/AwsDynamoDB.node.ts

417 lines
12 KiB
TypeScript
Raw Normal View History

refactor: Apply more `eslint-plugin-n8n-nodes-base` rules (#3624) * :arrow_up: Upgrade `eslint-plugin-n8n-nodes-base` * :package: Update `package-lock.json` * :wrench: Adjust renamed filesystem rules * :pencil2: Alphabetize ruleset * :zap: Categorize overrides * :zap: Set renamings in lint exceptions * :zap: Run baseline `lintfix` * :zap: Update linting scripts * :shirt: Apply `node-param-description-missing-from-dynamic-multi-options` * :shirt: Apply `cred-class-field-name-missing-oauth2` (#3627) * Rule working as intended * Removed comments * Move cred rule to different rule set * :shirt: Apply `node-param-array-type-assertion` * :shirt: Apply `node-dirname-against-convention` * Apply `cred-class-field-display-name-oauth2` (#3628) * Apply `node-execute-block-wrong-error-thrown` * Apply `node-class-description-display-name-unsuffixed-trigger-node` * Apply `node-class-description-name-unsuffixed-trigger-node` * Apply `cred-class-name-missing-oauth2-suffix` (#3636) * Rule working as intended, add exception to existing nodes * :shirt: Apply `cred-class-field-name-uppercase-first-char` (#3638) * :arrow_up: Upgrade to plugin version 1.2.28 * :package: Update `package-lock.json` * :shirt: Update lintings with 1.2.8 change * :shirt: Apply `cred-class-field-name-unsuffixed` * :shirt: Apply `cred-class-name-unsuffixed` * :shirt: Apply `node-class-description-credentials-name-unsuffixed` * :pencil2: Alphabetize rules * :heavy_minus_sign: Remove `nodelinter` package * :package: Update `package-lock.json` * :zap: Consolidate `lint` and `lintfix` scripts Co-authored-by: agobrech <45268029+agobrech@users.noreply.github.com> Co-authored-by: agobrech <ael.gobrecht@gmail.com>
2022-07-04 02:12:08 -07:00
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import type {
IExecuteFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeParameterValue,
} from 'n8n-workflow';
import { awsApiRequest, awsApiRequestAllItems } from './GenericFunctions';
import { itemFields, itemOperations } from './ItemDescription';
import type {
FieldsUiValues,
IAttributeNameUi,
IAttributeValue,
IAttributeValueUi,
IRequestBody,
PutItemUi,
} from './types';
import {
adjustExpressionAttributeName,
adjustExpressionAttributeValues,
adjustPutItem,
decodeItem,
simplify,
} from './utils';
export class AwsDynamoDB implements INodeType {
description: INodeTypeDescription = {
displayName: 'AWS DynamoDB',
name: 'awsDynamoDb',
icon: 'file:dynamodb.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume the AWS DynamoDB API',
defaults: {
name: 'AWS DynamoDB',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'aws',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
refactor: Apply more nodelinting rules (#3324) * :pencil2: Alphabetize lint rules * :fire: Remove duplicates * :zap: Update `lintfix` script * :shirt: Apply `node-param-operation-without-no-data-expression` (#3329) * :shirt: Apply `node-param-operation-without-no-data-expression` * :shirt: Add exceptions * :shirt: Apply `node-param-description-weak` (#3328) Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-option-value-duplicate` (#3331) * :shirt: Apply `node-param-description-miscased-json` (#3337) * :shirt: Apply `node-param-display-name-excess-inner-whitespace` (#3335) Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-type-options-missing-from-limit` (#3336) * Rule workig as intended * :pencil2: Uncomment rules Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-option-name-duplicate` (#3338) Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-description-wrong-for-simplify` (#3334) * :zap: fix * :zap: exceptions * :zap: changed rule ignoring from file to line * :shirt: Apply `node-param-resource-without-no-data-expression` (#3339) * :shirt: Apply `node-param-display-name-untrimmed` (#3341) * :shirt: Apply `node-param-display-name-miscased-id` (#3340) Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-resource-with-plural-option` (#3342) * :shirt: Apply `node-param-description-wrong-for-upsert` (#3333) * :zap: fix * :zap: replaced record with contact in description * :zap: fix Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :shirt: Apply `node-param-option-description-identical-to-name` (#3343) * :shirt: Apply `node-param-option-name-containing-star` (#3347) * :shirt: Apply `node-param-display-name-wrong-for-update-fields` (#3348) * :shirt: Apply `node-param-option-name-wrong-for-get-all` (#3345) * :zap: fix * :zap: exceptions * :shirt: Apply node-param-display-name-wrong-for-simplify (#3344) * Rule working as intended * Uncomented other rules * :shirt: Undo and add exceptions Co-authored-by: Iván Ovejero <ivov.src@gmail.com> * :zap: Alphabetize lint rules * :zap: Restore `lintfix` script Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com> Co-authored-by: agobrech <45268029+agobrech@users.noreply.github.com>
2022-05-20 14:47:24 -07:00
noDataExpression: true,
options: [
{
name: 'Item',
value: 'item',
},
],
default: 'item',
},
...itemOperations,
...itemFields,
],
};
methods = {
loadOptions: {
async getTables(this: ILoadOptionsFunctions) {
const headers = {
'Content-Type': 'application/x-amz-json-1.0',
'X-Amz-Target': 'DynamoDB_20120810.ListTables',
};
const responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', {}, headers);
return responseData.TableNames.map((table: string) => ({ name: table, value: table }));
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
let responseData;
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
if (resource === 'item') {
if (operation === 'upsert') {
// ----------------------------------
// upsert
// ----------------------------------
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html
const eavUi = this.getNodeParameter(
'additionalFields.eavUi.eavValues',
i,
[],
) as IAttributeValueUi[];
const conditionExpession = this.getNodeParameter(
'conditionExpression',
i,
'',
) as string;
const eanUi = this.getNodeParameter(
'additionalFields.eanUi.eanValues',
i,
[],
) as IAttributeNameUi[];
const body: IRequestBody = {
TableName: this.getNodeParameter('tableName', i) as string,
};
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
if (Object.keys(expressionAttributeValues).length) {
body.ExpressionAttributeValues = expressionAttributeValues;
}
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
if (Object.keys(expressionAttributeName).length) {
body.expressionAttributeNames = expressionAttributeName;
}
if (conditionExpession) {
body.ConditionExpression = conditionExpession;
}
const dataToSend = this.getNodeParameter('dataToSend', 0) as
| 'defineBelow'
| 'autoMapInputData';
const item: { [key: string]: string } = {};
if (dataToSend === 'autoMapInputData') {
const incomingKeys = Object.keys(items[i].json);
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
const inputsToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
for (const key of incomingKeys) {
if (inputsToIgnore.includes(key)) continue;
item[key] = items[i].json[key] as string;
}
body.Item = adjustPutItem(item as PutItemUi);
} else {
const fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
fields.forEach(({ fieldId, fieldValue }) => (item[fieldId] = fieldValue));
body.Item = adjustPutItem(item as PutItemUi);
}
const headers = {
'Content-Type': 'application/x-amz-json-1.0',
'X-Amz-Target': 'DynamoDB_20120810.PutItem',
};
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
responseData = item;
} else if (operation === 'delete') {
// ----------------------------------
// delete
// ----------------------------------
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html
const body: { [key: string]: any } = {
TableName: this.getNodeParameter('tableName', i) as string,
Key: {},
ReturnValues: this.getNodeParameter('returnValues', 0) as string,
};
const eavUi = this.getNodeParameter(
'additionalFields.eavUi.eavValues',
i,
[],
) as IAttributeValueUi[];
const eanUi = this.getNodeParameter(
'additionalFields.eanUi.eanValues',
i,
[],
) as IAttributeNameUi[];
const additionalFields = this.getNodeParameter('additionalFields', i);
const simple = this.getNodeParameter('simple', 0, false) as boolean;
const keyValues = this.getNodeParameter('keysUi.keyValues', i, []) as [
{ key: string; type: string; value: string },
];
for (const item of keyValues) {
let value = item.value as NodeParameterValue;
// All data has to get send as string even numbers
// @ts-ignore
value = ![null, undefined].includes(value) ? value?.toString() : '';
body.Key[item.key] = { [item.type]: value };
}
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
if (Object.keys(expressionAttributeValues).length) {
body.ExpressionAttributeValues = expressionAttributeValues;
}
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
if (Object.keys(expressionAttributeName).length) {
body.expressionAttributeNames = expressionAttributeName;
}
const headers = {
'Content-Type': 'application/x-amz-json-1.0',
'X-Amz-Target': 'DynamoDB_20120810.DeleteItem',
};
if (additionalFields.conditionExpression) {
body.ConditionExpression = additionalFields.conditionExpression as string;
}
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
if (!Object.keys(responseData as IDataObject).length) {
responseData = { success: true };
} else if (simple) {
responseData = decodeItem(responseData.Attributes as IAttributeValue);
}
} else if (operation === 'get') {
// ----------------------------------
// get
// ----------------------------------
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html
const tableName = this.getNodeParameter('tableName', 0) as string;
const simple = this.getNodeParameter('simple', 0, false) as boolean;
const select = this.getNodeParameter('select', 0) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const eanUi = this.getNodeParameter(
'additionalFields.eanUi.eanValues',
i,
[],
) as IAttributeNameUi[];
const body: { [key: string]: any } = {
TableName: tableName,
Key: {},
Select: select,
};
Object.assign(body, additionalFields);
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
if (Object.keys(expressionAttributeName).length) {
body.expressionAttributeNames = expressionAttributeName;
}
if (additionalFields.readType) {
body.ConsistentRead = additionalFields.readType === 'stronglyConsistentRead';
}
if (additionalFields.projectionExpression) {
body.ProjectionExpression = additionalFields.projectionExpression as string;
}
const keyValues = this.getNodeParameter('keysUi.keyValues', i, []) as IDataObject[];
for (const item of keyValues) {
let value = item.value as NodeParameterValue;
// All data has to get send as string even numbers
// @ts-ignore
value = ![null, undefined].includes(value) ? value?.toString() : '';
body.Key[item.key as string] = { [item.type as string]: value };
}
const headers = {
'X-Amz-Target': 'DynamoDB_20120810.GetItem',
'Content-Type': 'application/x-amz-json-1.0',
};
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
responseData = responseData.Item;
if (simple && responseData) {
responseData = decodeItem(responseData as IAttributeValue);
}
} else if (operation === 'getAll') {
// ----------------------------------
// getAll
// ----------------------------------
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html
const eavUi = this.getNodeParameter('eavUi.eavValues', i, []) as IAttributeValueUi[];
const simple = this.getNodeParameter('simple', 0, false) as boolean;
const select = this.getNodeParameter('select', 0) as string;
const returnAll = this.getNodeParameter('returnAll', 0);
const scan = this.getNodeParameter('scan', 0) as boolean;
const eanUi = this.getNodeParameter(
'options.eanUi.eanValues',
i,
[],
) as IAttributeNameUi[];
const body: IRequestBody = {
TableName: this.getNodeParameter('tableName', i) as string,
};
if (scan) {
const filterExpression = this.getNodeParameter('filterExpression', i) as string;
if (filterExpression) {
body.FilterExpression = filterExpression;
}
} else {
body.KeyConditionExpression = this.getNodeParameter(
'keyConditionExpression',
i,
) as string;
}
const { indexName, projectionExpression, filterExpression } = this.getNodeParameter(
'options',
i,
) as {
indexName: string;
projectionExpression: string;
filterExpression: string;
};
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
if (Object.keys(expressionAttributeName).length) {
body.ExpressionAttributeNames = expressionAttributeName;
}
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
if (Object.keys(expressionAttributeValues).length) {
body.ExpressionAttributeValues = expressionAttributeValues;
}
if (indexName) {
body.IndexName = indexName;
}
if (projectionExpression && select !== 'COUNT') {
body.ProjectionExpression = projectionExpression;
}
if (filterExpression) {
body.FilterExpression = filterExpression;
}
if (select) {
body.Select = select;
}
const headers = {
'Content-Type': 'application/json',
'X-Amz-Target': scan ? 'DynamoDB_20120810.Scan' : 'DynamoDB_20120810.Query',
};
if (returnAll && select !== 'COUNT') {
responseData = await awsApiRequestAllItems.call(
this,
'dynamodb',
'POST',
'/',
body,
headers,
);
} else {
body.Limit = this.getNodeParameter('limit', 0, 1);
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
if (select !== 'COUNT') {
responseData = responseData.Items;
}
}
if (simple) {
responseData = responseData.map(simplify);
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
} catch (error) {
if (this.continueOnFail()) {
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: i } },
);
returnData.push(...executionData);
continue;
}
throw error;
}
}
return this.prepareOutputData(returnData);
}
}