NodeJS ValidationException: The provided key element does not match the schema - node.js

I know this has been asked before but I've tried everything from previous posts and still couldn't find a solution for my problem. I can query the table from DynamoDB no problem but can't from lambda. I deleted and created the table with the same name to get rid of a sort key hoping to fix my issue but it did not fix it too. So here is my code
Params
const queryParams = {
TableName: tableName,
KeyConditionExpression: 'postId = :postId',
ExpressionAttributeValues: { ":postId": postId }
};
Schema
type LikeRelationship
#model(
mutations: {
create: "createLikeRelationship"
delete: "deleteLikeRelationship"
update: null
}
timestamps: null
)
#auth(
rules: [
{
allow: owner
ownerField: "likerId"
operations: [read, create, delete]
}
{ allow: public, provider: iam, operations: [read] }
]
)
#key(fields: ["postId"]) {
postId: ID!
postOwnerId: String!
likerType: String
likerName: String
likerId: ID!
timestamp: Int!
}
Full Lambda Code
/* Amplify Params - DO NOT EDIT
API_GROOVESECONDED_GRAPHQLAPIIDOUTPUT
API_GROOVESECONDED_LIKERELATIONSHIPTABLE_ARN
API_GROOVESECONDED_LIKERELATIONSHIPTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
exports.handler = async (event, context, callback) => {
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });
const tableName = 'LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging';
async function deleteItems(tableName, postId ) {
const queryParams = {
TableName: tableName,
KeyConditionExpression: 'postId = :postId',
ExpressionAttributeValues: { ":postId": postId }
};
const queryResults = await docClient.query(queryParams).promise();
if (queryResults.Items && queryResults.Items.length > 0) {
const batchCalls = chunks(queryResults.Items, 25).map( async (chunk) => {
const deleteRequests = chunk.map( item => {
return {
DeleteRequest : {
Key : {
'partitionId' : item.partitionId,
'sortId' : item.sortId,
}
}
};
}
);
const batchWriteParams = {
RequestItems : {
[tableName] : deleteRequests
}
};
await docClient.batchWrite(batchWriteParams).promise();
});
await Promise.all(batchCalls);
}
}
if (event.body !== null && event.body !== undefined) {
let data = JSON.parse(event.body);
if (typeof data.postId === 'undefined') {
return sendRes(404, '{ error: true, message: "postId undefined." }');
}
try {
await deleteItems(tableName, data.postId );
} catch (e) {
return sendRes(404, '{ error: true, message: " ' + e + data.postId + typeof data.postId + ' " }');
}
return sendRes(200, '{ "error": false, "message": "Success" }');
}
return sendRes(404, '{ error: true, message: "Event body null or undefined." }');
};
const sendRes = (status, body) => {
var response = {
statusCode: status,
headers: {
"Content-Type" : "application/json",
"Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Methods" : "OPTIONS,POST",
"Access-Control-Allow-Credentials" : true,
"Access-Control-Allow-Origin" : "*",
"X-Requested-With" : "*"
},
body: body
};
return response;
};
// https://stackoverflow.com/a/37826698/3221253
function chunks(inputArray, perChunk) {
return inputArray.reduce((all,one,i) => {
const ch = Math.floor(i/perChunk);
all[ch] = [].concat((all[ch]||[]),one);
return all;
}, []);
}
IAM Role
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:Delete*",
"dynamodb:Get*",
"dynamodb:Scan",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging",
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging/index/*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"dynamodb:List*",
"dynamodb:Describe*"
],
"Resource": [
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging",
"arn:aws:dynamodb:eu-central-1:382762466424:table/LikeRelationship-76mphdcsf5cijbgq5kkcny3xpe-staging/index/*"
]
}
]
}

Related

AWS Update item with list on DynamoDB using lambda Nodejs

I have an item in my DynamoDB table that looks like this:
{
"qid": {
"S": "0"
},
"options": {
"L": [
{
"S": "Summer"
},
{
"S": "Winter"
}
]
},
"votes": {
"L": [
{
"N": "11"
},
{
"N": "13"
}
]
}
}
With a lambda function, I want to update one of the elements in the votes list. I want to update the index that matches the index of the option I get from the event (the event has qid, option and number of votes). So here is what I tried:
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
const qid = event.qid;
const option = event.option;
const vote = event.vote;
const params = {
TableName : 'QnV',
Key: {
qid: qid
}
}
try {
const data = await docClient.get(params).promise()
const options = data.Item.options;
const index = options.findIndex(option)
const updateParams = {
TableName: 'QnV',
Key: {
'qid': qid
},
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
ReturnValues: 'UPDATED_NEW'
};
const updateData = await docClient.update(updateParams).promise();
return {
statusCode: 200,
body: JSON.stringify(updateData)
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
};
I tried to test it with this:
{
"qid": "0",
"option": "Winter",
"vote": 14
}
But I get an error of 500 and an empty body. It looks like the update params variable is empty, but I don't understand why.
I tried to search online but it looks like I'm doing everything correctly (but if that was the case I didn't have a problem). I also didn't forget to grant permission to the function.
Edit: As suggested, I change the body of the error to err.message, and got this:
{
"statusCode": 500,
"body": "\"Winter is not a function\""
}
Please help me find the bug, I would really appreciate it!
Thank you
After more debugging I found the problems. There were two.
Incorrect index method.
const index = options.findIndex(option)
Changed to:
const index = options.indexOf(option)
The way I tried to find the index in updateParams was incorrect. Instead of:
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
It should be:
UpdateExpression: `set votes[${index}] = :vote`,
ExpressionAttributeValues: {
':vote': {N: vote},
},

Node - build a tree recursively with API data

I need to build a tree like structure using data from an API.
The structure i start with is as follows:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [],
}
There will always be a root group as the base of the tree.
I have a function named getMembersInGroup(groupId) which is an API call and returns something like:
[
{
"type": "group",
"id": 77,
"name": "IT group",
},
{
"type": "user",
"id": 40,
"name": "John"
}
]
Members can either be of type user or another group. So a user would look like:
{
"type": "user",
"id": 40,
"name": "John"
}
If it's another group it needs to recursively fetch those until there are only users or empty array left in members.
Any group can have users at any level with the tree.
A mock of getMembersInGroup:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
The end result should look like this:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [
{
"type": "group",
"id": 88,
"name": "Some group",
"members": [
{
"type": "user",
"id": 231,
"name": "SALLY"
},
{
"type": "user",
"id": 232,
"name": "Henry"
}
]
},
{
"type": "user",
"id": 41,
"name": "Chris"
}
],
}
I need help with the algorithm to create the tree.
Your getMembersInGroup function could look like this:
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi(`/groups/${groupId}/members`)) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
Call it like this:
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
loadTree().then(result =>
console.log(result);
// Work with the result ...
).catch(error =>
console.log("error: ", error)
);
Demo with a mock implementation of fetch.callApi:
// Mock for fetch.callApi
const fetch = {
mockData: [0,[2,3,4],[5,6,7],[8,9],0,0,0,[10],0,0,[11,12],0,0],
callApi(url) {
return new Promise((resolve, reject) => {
const groupId = +url.split("/")[2];
const children = this.mockData[groupId];
if (!children) return reject("not found: " + groupId);
const result = children.map(id => {
const type = this.mockData[id] ? "group" : "user";
return {type, id, name: type + "_" + id};
});
setTimeout(() => resolve(result), 50);
});
}
}
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi('/groups/' + groupId + '/members')) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
loadTree().then(result =>
console.log(JSON.stringify(result, null, 2))
).catch(error =>
console.log("error: ", error)
);
You can do something like:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
foreach(member in members) {
if (member.type == 'groups') {
member.members = getMembersInGroup(member.groupid)
}
}
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
So you have the recursion only if it's a group type, otherwise the member is returned as is.

Apollo Server Resolver not returning all data (returned data is not complete)

My setup: Apollo server with express.js
MongoDB with Mongoose
My problem: When I run a query, my resolver is not fetching all of the data, just part of it.
Here is my resolver code:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
contentOut.entry = [{ entryVideos: [] }];
content.entry.map(async (val) => {
let file = await Asset.findById(val, 'uri');
if (file && file.uri) {
contentOut.entry[0].entryVideos.push(
file.uri.split('/').slice(-1)[0]
);
}
});
// EQUIPMENT
contentOut.equipment = [];
content.equipment.map(async (val) => {
let equipment = await MarsEquipment.findById(
val.id,
'name thumbnail background'
);
if (equipment) {
contentOut.equipment.push({
id: val.id,
name: equipment.name,
panelImage: equipment.thumbnail.split('/').slice(-1)[0],
productImage: equipment.background.split('/').slice(-1)[0],
});
}
});
// EXERCISES
contentOut.exercises = [];
content.exercises.map(async (val, index) => {
contentOut.exercises.push({
equipment: val.equipment,
content: [],
});
val.content.map(async (valC) => {
let exercise = await MarsExercise.findById(
valC.id,
'name level text thumbnail video'
);
if (exercise) {
let instructions = [];
for (const [key, value] of Object.entries(
JSON.parse(exercise.text)
)) {
instructions.push(value);
}
contentOut.exercises[index].content.push({
id: valC.id,
position: valC.position,
name: exercise.name,
level: exercise.level,
instructions: instructions,
panelImage: exercise.thumbnail.split('/').slice(-1)[0],
programVideo: exercise.video.split('/').slice(-1)[0],
});
}
});
});
// OPTIONS
contentOut.options = [];
let bgImage = await Asset.findById(content.options[0].bgImage, 'uri');
bgImage = bgImage.uri.split('/').slice(-1)[0];
contentOut.options = [
{
bgImage: bgImage,
cards: [],
},
];
content.options[0].cards.map(async (val, index) => {
let cardImg = await Asset.findById(val.panelImage, 'uri');
if (cardImg) {
contentOut.options[0].cards.push({
name: val.name,
panelImage: cardImg.uri.split('/').slice(-1)[0],
subheading: val.subheading,
action: val.action,
});
if (val.overlay) {
contentOut.options[0].cards[index].overlay = val.overlay;
}
if (
val.externalApp &&
val.externalApp.appName &&
val.externalApp.playStoreId
) {
contentOut.options[0].cards[index].externalApp = {
appName: val.externalApp.appName,
playStoreId: val.externalApp.playStoreId,
};
}
}
});
// WORKOUTS
contentOut.workouts = [];
content.workouts.map(async (val) => {
let workout = await MarsWorkout.findById(
val.id,
'name thumbnail video text required'
);
if (workout) {
contentOut.workouts.push({
id: val.id,
position: val.position,
name: workout.name,
panelImage: workout.thumbnail.split('/').slice(-1)[0],
programVideo: workout.video.split('/').slice(-1)[0],
instructions: workout.text,
required: workout.required,
});
}
});
// FILES
contentOut.files = [];
content.files.map(async (val) => {
let file = await Asset.findById(val, 'uri updated_at');
if (file) {
contentOut.files.push({
id: val,
uri: file.uri,
filename: file.uri.split('/').slice(-1)[0],
timestamp: file.updated_at,
});
}
});
return contentOut;
} else {
return {};
}
}
}
Here is the query I'm running in the Playground:
query {
getMarsContentForScreen(screen: "GS123123123123", token: "token-here") {
id
name
entry {
entryVideos
}
equipment {
id
name
position
panelImage
productImage
}
exercises {
equipment
content {
id
position
name
level
panelImage
programVideo
instructions
}
}
options {
bgImage
cards {
name
panelImage
subheading
action
overlay
externalApp {
appName
playStoreId
}
}
}
workouts {
id
position
name
panelImage
programVideo
required
instructions
}
files {
id
filename
uri
timestamp
}
}
}
And here is the output of what I'm getting:
{
"data": {
"getMarsContentForScreen": {
"id": "6203d63f54a0bd82832288c5",
"name": "sdfgsdfg",
"entry": [
{
"entryVideos": [
"6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"9b1628af-e69e-4d0e-9d53-b472a963a1ec.mp4",
"830b0258-70f1-4206-b07b-fb60508e33c5.mp4"
]
}
],
"equipment": [
{
"id": "62025aa4237005069c569d63",
"name": "dsfgsdfg",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025afa237005069c569d99",
"name": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025af4237005069c569d92",
"name": "sdfgsdfgsdfgdsf",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
}
],
"exercises": [
{
"equipment": "dsfgsdfg",
"content": [
{
"id": "62025b27237005069c569dc0",
"position": 1,
"name": "sdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfgsdfg",
"sdfg"
]
},
{
"id": "62025b30237005069c569dc7",
"position": 2,
"name": "sdfgsdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfg",
"hgfjgh"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgdsf",
"content": [
{
"id": "62025b80237005069c569e13",
"position": 1,
"name": "sdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfg",
"sdfgsdfg",
"sdfgdf"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"content": [
{
"id": "62025b88237005069c569e1a",
"position": 1,
"name": "uitytyui",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"ytuityui",
"tyui",
"tyuityuityui"
]
}
]
}
],
"options": [
{
"bgImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"cards": []
}
],
"workouts": [],
"files": []
}
}
}
As you can see, everything from "options" : [{"cards"}] is empty, but it shouldn't be, as there is the data in the database for it.
What is even more interesting, is that when I console.log the contentOut object inside the last .map function (content.files.map()) I'm getting the full response.
Basically it looks like my resolver is returning the content before all of it is gathered.
If I add some if statement to check if all of my content is in the contentOut object, I'm getting empty response, just like the resolver couldn't be bothered to wait for all of the content...
Any ideas?
Many thanks in advance!
Ok, so after more Googling and fighting with it, I've re-write the whole code and use Promise.all for each part of the function in order to make sure that it will wait for the outcome of each await, before returning the value.
Now the code looks like this:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
const getEntryVideos = async (content) => {
let result = [{ entryVideos: [] }];
await Asset.find({ _id: { $in: content } }, 'uri').then((response) =>
response.map((val) => {
result[0].entryVideos.push(val.uri.split('/').slice(-1)[0]);
})
);
return result;
};
const getEquipment = async (content) => {
let result = [];
const ids = content.map((val) => {
return val.id;
});
await MarsEquipment.find(
{ _id: { $in: ids } },
'id name thumbnail background'
).then((response) =>
response.map((val) => {
result.push({
id: val.id,
name: val.name,
panelImage: val.thumbnail.split('/').slice(-1)[0],
productImage: val.background.split('/').slice(-1)[0],
});
})
);
return result;
};
const getExercises = async (content) => {
let result = [];
const ids = [].concat(
...content.map((val) => {
result.push({
equipment: val.equipment,
content: [],
});
return val.content.map((valC) => {
return valC.id;
});
})
);
await MarsExercise.find(
{ _id: { $in: ids } },
'id name level text thumbnail video product'
).then((response) =>
response.map((exer) => {
let instructions = [];
const index = result.indexOf(
result.find((equip) => equip.equipment === exer.product)
);
for (const [key, value] of Object.entries(JSON.parse(exer.text))) {
instructions.push(value);
}
result[index].content.push({
id: exer.id,
position: exer.position,
name: exer.name,
level: exer.level,
instructions: instructions,
panelImage: exer.thumbnail.split('/').slice(-1)[0],
programVideo: exer.video.split('/').slice(-1)[0],
});
})
);
return result;
};
const getOptions = async (content) => {
let result = content;
const ids = content[0].cards.map((val) => {
return val.panelImage;
});
await Asset.findById(content[0].bgImage, 'uri').then((response) => {
result[0].bgImage = response.uri.split('/').slice(-1)[0];
});
await Asset.find({ _id: { $in: ids } }, 'id uri').then((response) =>
response.map((val) => {
let index = result[0].cards.indexOf(
result[0].cards.find((card) => card.panelImage === val.id)
);
result[0].cards[index].panelImage = val.uri.split('/').slice(-1)[0];
})
);
return result;
};
const getWorkouts = async (content) => {
let result = content;
const ids = content.map((val) => {
return val.id;
});
await MarsWorkout.find(
{ _id: { $in: ids } },
'id name thumbnail video text required'
).then((response) => {
response.map((val) => {
let index = result.indexOf(
result.find((work) => work.id === val.id)
);
result[index].panelImage = val.thumbnail.split('/').slice(-1)[0];
result[index].programVideo = val.video.split('/').slice(-1)[0];
});
});
return result;
};
const getFiles = async (content) => {
let result = [];
await Asset.find({ _id: { $in: content } }, 'id uri updated_at').then(
(response) => {
response.map((val) => {
result.push({
id: val.id,
uri: val.uri,
filename: val.uri.split('/').slice(-1)[0],
timestamp: val.updated_at,
});
});
}
);
return result;
};
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
const entry = getEntryVideos(content.entry);
// EQUIPMENT
const equipment = getEquipment(content.equipment);
// EXERCISES
const exercises = getExercises(content.exercises);
// OPTIONS
const options = getOptions(content.options);
// WORKOUTS
const workouts = getWorkouts(content.workouts);
// FILES
const files = getFiles(content.files);
// PROMISE
const results = await Promise.all([
entry,
equipment,
exercises,
options,
workouts,
files,
]);
//console.log(results);
return {
id: content.id,
name: content.name,
entry: results[0],
equipment: results[1],
exercises: results[2],
options: results[3],
workouts: results[4],
files: results[5],
};
} else {
return {};
}
}
},

How to write data to local DynamoDB?

According to DynamoDB docs, you always need to specify --endpoint-url http://localhost:8000 option if you want to interact with your local (downloaded) version of DynamoDB. It works fine for me via aws cli, but, following their tutorial for Node.js developers, I've faced a strange issue: I have been interacting with data in DynamoDB web version instead of my local one, even if configured connection options to database:
const serviceConfigOptions: ServiceConfigurationOptions = {
region: "eu-north-1",
endpoint: "http://localhost:8000",
};
I described endpoint, but I have no results interacting with data in my local version and I have these in the web.
All code is here:
import fs from "fs";
import AWS, { AWSError } from "aws-sdk";
import { config as dotenvConfig } from "dotenv";
import {
CreateTableInput,
DocumentClient,
PutItemInput,
PutItemOutput,
} from "aws-sdk/clients/dynamodb";
import { ServiceConfigurationOptions } from "aws-sdk/lib/service";
dotenvConfig();
const serviceConfigOptions: ServiceConfigurationOptions = {
region: "eu-north-1",
endpoint: "http://localhost:8000",
};
const dynamoDB: AWS.DynamoDB = new AWS.DynamoDB();
const params: CreateTableInput = {
TableName: "Movies",
KeySchema: [
{
AttributeName: "year",
KeyType: "HASH",
},
{
AttributeName: "title",
KeyType: "RANGE",
},
],
AttributeDefinitions: [
{ AttributeName: "year", AttributeType: "N" },
{ AttributeName: "title", AttributeType: "S" },
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10,
},
};
dynamoDB.createTable(params, (err, data): void => {
if (err) {
console.log("Unable to create table.");
console.error(err);
return;
}
console.log("Table created");
console.log(data);
});
const docClient: DocumentClient = new AWS.DynamoDB.DocumentClient();
console.log("Imporing data to DynamoDB");
fs.readFile(
"moviedata.json",
"utf-8",
(err: NodeJS.ErrnoException | null, data: string): void => {
if (err) {
console.log("Unable to read a file");
console.error(err);
return;
}
const allMovies: Array<any> = JSON.parse(data);
allMovies.forEach(({ year, title, info }): void => {
const params: PutItemInput = {
TableName: "Movies",
Item: {
year,
title,
info,
},
};
docClient.put(params, (err: AWSError, data: PutItemOutput) => {
if (err) {
console.error(
"Unable to add movie",
title,
". Error JSON:",
JSON.stringify(err, null, 2),
);
return;
}
console.log("PutItem succeeded:", title);
});
});
},
);
UPDATE:
Refactored code into promises.
import fs from "fs";
import { promisify } from "util";
import AWS, { AWSError } from "aws-sdk";
import { config as dotenvConfig } from "dotenv";
import {
CreateTableInput,
DocumentClient,
PutItemInput,
PutItemOutput,
} from "aws-sdk/clients/dynamodb";
import { ServiceConfigurationOptions } from "aws-sdk/lib/service";
dotenvConfig();
const serviceConfigOptions: ServiceConfigurationOptions = {
region: "eu-north-1",
endpoint: "http://localhost:8000",
};
const dynamoDB: AWS.DynamoDB = new AWS.DynamoDB();
const params: CreateTableInput = {
TableName: "Movies",
KeySchema: [
{
AttributeName: "year",
KeyType: "HASH",
},
{
AttributeName: "title",
KeyType: "RANGE",
},
],
AttributeDefinitions: [
{ AttributeName: "year", AttributeType: "N" },
{ AttributeName: "title", AttributeType: "S" },
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10,
},
};
const createDBTable: (
params: AWS.DynamoDB.CreateTableInput,
) => Promise<AWS.DynamoDB.CreateTableOutput> = promisify(
dynamoDB.createTable,
).bind(dynamoDB);
const readFile = promisify(fs.readFile);
createDBTable(params)
.then(data => {
console.log("Table created");
console.log(data);
})
.catch(err => {
console.log("Unable to create table.");
throw new Error(err);
})
.then(() => {
console.log("Imporing data to DynamoDB");
return readFile("moviedata.json", "utf-8");
})
.then((data: string) => {
const allMovies: Array<any> = JSON.parse(data);
const docClient: DocumentClient = new AWS.DynamoDB.DocumentClient();
allMovies.forEach(({ year, title, info }): void => {
const params: PutItemInput = {
TableName: "Movies",
Item: {
year,
title,
info,
},
};
docClient.put(params, (err: AWSError, data: PutItemOutput) => {
if (err) {
console.error(
"Unable to add movie",
title,
". Error JSON:",
JSON.stringify(err, null, 2),
);
return;
}
console.log("PutItem succeeded:", title);
});
});
})
.catch(err => {
console.error(err);
});
Now I'm getting Unable to create table. Error: ResourceInUseException: Table already exists: Movies. However, in my local db only one Music table.
$ aws dynamodb list-tables --endpoint-url http://localhost:8000
Out:
{
"TableNames": [
"Music"
]
}
P.S. AWS_SDK_LOAD_CONFIG=1
I found the mistake I made.
I've configured params
const serviceConfigOptions: ServiceConfigurationOptions = {
region: "eu-north-1",
endpoint: "http://localhost:8000",
};
but I forgot to pass it to const dynamoDB: AWS.DynamoDB = new AWS.DynamoDB(); as an argument.
Must be
const serviceConfigOptions: ServiceConfigurationOptions = {
region: "eu-north-1",
endpoint: "http://localhost:8000",
};
const dynamoDB: AWS.DynamoDB = new AWS.DynamoDB(serviceConfigOptions);

Graphql not resolving as expected

I´m developing a GraqphQL-API which gets the data from MongoDB via Mongoose. Now I have the problem that GraphQL does not use the query to resolve the fields, instead it uses the field resolvers which can't work because there is no ID set.
TypeDefs:
type Query{
getUser(user_id: String!): User
getGroup(group_id: String!): Group
}
type Group {
name: String
scopes: [Scope]
id: String
}
The Resolver:
Query: {
getUser: (root,args,context) => {
console.log(args);
return getUser(args.user_id);
},
getGroup: (root,args,context) => {
return getGroup(args.group_id);
}
},
Group: {
scopes(group){
return getScopes(group.id);
},
name(group){
return getGroupName(group.id);
}
},
The Functions:
exports.getGroup = async (id) => {
const g = await Group.findOne({_id:id});
const scopes = [];
for(let index in g.scopes){
scopes.push({id: g.scopes[index]});
}
console.log(g.name);
return{
name: g.name,
scopes: scopes
};
};
exports.getGroupName = async (id) => {
const g = await Group.findOne({_id:id});
console.log(id);
return g.name;
};
exports.getScopes = async (id) => {
const g = await Group.findOne({_id:id});
const scopes = [];
for(let index in g.scopes){
scopes.push({id: g.scopes[index]});
}
return scopes;
};
The Query:
query getUserGroup{
getGroup(group_id:"5b74c537086152554adf818e"){name}
}
The Result:
{
"data": {
"getGroup": {
"name": null
}
},
"errors": [
{
"message": "Cannot read property 'name' of null",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"getGroup",
"name"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read property 'name' of null",
" at _callee2$ (/XXXX/resolver/group.js:20:14)",
" at tryCatch (/XXXX/node_modules/regenerator-
runtime/runtime.js:65:40)",
" at Generator.invoke [as _invoke]
(XXXX/node_modules/regenerator-runtime/runtime.js:303:22)",
" at Generator.prototype.(anonymous function) [as next]
(XXXXX/node_modules/regenerator-runtime/runtime.js:117:21)",
" at step (XXXXX/src/resolver/group.js:5:191)",
" at XXXX/resolver/group.js:5:361",
" at <anonymous>"
]
}
}
}
]
}
const g = await Group.findOne({_id:id});
"TypeError: Cannot read property 'name' of null"
"path": [
"getGroup",
"name"
],
Can you check if g exists or not?

Resources