Compare commits

...

10 Commits

Author SHA1 Message Date
Ben Ramey
ac254986b1 refactor to include both get and post in one function 2018-09-20 06:59:22 -05:00
Ben Ramey
4e114f8de4 move files around 2018-09-20 06:46:02 -05:00
Ben Ramey
8eba063aef Working get and post endpoitns 2018-09-19 06:58:17 -05:00
Ben Ramey
07e4a27a1a courses get and post 2018-09-19 06:16:25 -05:00
Ben Ramey
d08bc9cb56 Fix scan 2018-09-18 20:09:19 -05:00
Ben Ramey
136a6f4df0 Trying to get courses scan to work 2018-09-18 07:38:12 -05:00
Ben Ramey
7710a4ef14 Working (?) courses get func 2018-09-18 07:34:12 -05:00
Ben Ramey
071038b7f8 WIP on courses get function 2018-09-18 07:14:04 -05:00
Ben Ramey
435060a764 Deployable hello function behind api gateway 2018-09-18 06:50:29 -05:00
Ben Ramey
5437fe88b0 Deployable hello function behind api gateway 2018-09-18 06:50:21 -05:00
9 changed files with 237 additions and 16 deletions

5
constants.js Normal file
View File

@@ -0,0 +1,5 @@
const COURSES_TABLE = "eg_courses";
module.exports = {
COURSES_TABLE
};

View File

@@ -1,14 +0,0 @@
'use strict';
module.exports.hello = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

38
handlers/courses/index.js Normal file
View File

@@ -0,0 +1,38 @@
const {createResponse} = require('../../helpers');
const {parseAndValidateBody} = require('../../validators');
const courseSchema = require('./schema');
const {COURSES_TABLE} = require('../../constants');
const db = require('../../services/db');
const get = async (event, context, callback) => {
const courses = await db.scan(COURSES_TABLE);
return createResponse(200, {
courses: courses
});
};
const post = async (event, context, callback) => {
const course = await parseAndValidateBody(courseSchema, event.body);
const createCourseResult = await db.create(COURSES_TABLE, course);
return createResponse(200, {
course: createCourseResult
});
};
module.exports.default = async (event, context, callback) => {
try {
if (event.httpMethod === "GET") {
return get(event, context, callback);
}
if (event.httpMethod === "POST") {
return post(event, context, callback);
}
} catch (err) {
const statusCode = (err && err.statusCode) || 500;
return createResponse(statusCode, err.message || err);
}
};

View File

@@ -0,0 +1,13 @@
module.exports.default = {
"type": "object",
"properties": {
"name": {"type": "string"},
"num_tests": {"type": "number"},
"num_questions_per_test": {"type": "number"},
"answers": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["name", "num_tests", "num_questions_per_test", "answers"]
};

20
helpers.js Normal file
View File

@@ -0,0 +1,20 @@
const customThrow = (statusCode, message) => {
const err = new Error();
err.statusCode = statusCode;
err.message = message;
throw err;
};
const createResponse = (statusCode, body) => ({
statusCode,
headers: {
"Access-Control-Allow-Origin" : "*", // Required for CORS support to work
"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
},
body: JSON.stringify(body),
});
module.exports = {
customThrow,
createResponse
};

View File

@@ -20,6 +20,17 @@ service: eg-api # NOTE: update this with your service name
provider:
name: aws
runtime: nodejs8.10
region: us-east-2
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "*"
# you can overwrite defaults here
# stage: dev
@@ -55,8 +66,17 @@ provider:
# - exclude-me-dir/**
functions:
hello:
handler: handler.hello
courses:
handler: handlers/courses.default
events:
- http:
path: courses
method: get
cors: true
- http:
path: courses
method: post
cors: true
# The following are a few example events you can configure
# NOTE: Please make sure to change your handler code to work with those events
@@ -102,3 +122,24 @@ functions:
# NewOutput:
# Description: "Description for the output"
# Value: "Some output value"
resources:
Resources:
coursesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: eg_courses
AttributeDefinitions:
- AttributeName: name
AttributeType: S
KeySchema:
- AttributeName: name
KeyType: HASH
Tags:
- Key: app
Value: ecs-grader
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
plugins:
- serverless-offline

View File

@@ -0,0 +1,13 @@
const AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'us-east-2'});
// also set local endpoint while in development
if (process.env.NODE_ENV === 'development') {
AWS.config.update({endpoint: "http://localhost:8000"});
}
const client = new AWS.DynamoDB.DocumentClient({});
module.exports = client;

70
services/db/index.js Normal file
View File

@@ -0,0 +1,70 @@
const dynamoClient = require('./dynamoClient');
const create = async (tableName, item) => {
const fullItem = {...item, created_at: Date.now()};
await dynamoClient.put({
TableName: tableName,
Item: fullItem
}).promise();
// put does not return saved item, just return our object
return fullItem;
};
const get = async (tableName, keyMap) => {
const res = await dynamoClient.get({
TableName: tableName,
Key: keyMap
}).promise();
return res && res.Item;
};
const scan = async (tableName) => {
const res = await dynamoClient.scan({
TableName: tableName
}).promise();
return res && res.Items;
};
const queryByID = async (tableName, idKeyName, id) => {
// querying by id can be used when we are querying a table
// that has a sortKey, but we don't need to use the sortKey
const res = await dynamoClient.query({
TableName: tableName,
KeyConditionExpression: "#id = :idValue",
ExpressionAttributeNames: {
"#id": idKeyName,
},
ExpressionAttributeValues: {
":idValue": id,
}
}).promise();
return res && res.Items;
};
const update = async (tableName, keyMap, ue, eav) => {
const res = await dynamoClient.update({
TableName: tableName,
Key: keyMap,
UpdateExpression: ue,
ExpressionAttributeValues: eav,
ReturnValues: 'UPDATED_NEW'
}).promise();
console.log('update res', res);
return res;
};
module.exports = {
client: dynamoClient,
create,
get,
scan,
queryByID,
update
};

35
validators/index.js Normal file
View File

@@ -0,0 +1,35 @@
const Ajv = require('ajv');
const {customThrow} = require('../helpers');
const parseAndValidateBody = async (schema, body) => {
let parsedBody;
try {
parsedBody = JSON.parse(body);
} catch (err) {
customThrow(400, 'Error. Malformed event.parsedBody.');
}
const validation = validateBody(schema, parsedBody);
if (!validation.isValid) {
customThrow(400, validation.error);
}
return parsedBody;
};
const validateBody = (schema, body) => {
const ajv = new Ajv({$data: true});
const validate = ajv.compile(schema);
const isValid = validate(body);
return {
isValid: isValid,
error: JSON.stringify(validate.errors)
}
};
module.exports = {
parseAndValidateBody,
};