Compare commits
10 Commits
ba58b1858d
...
ac254986b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac254986b1 | ||
|
|
4e114f8de4 | ||
|
|
8eba063aef | ||
|
|
07e4a27a1a | ||
|
|
d08bc9cb56 | ||
|
|
136a6f4df0 | ||
|
|
7710a4ef14 | ||
|
|
071038b7f8 | ||
|
|
435060a764 | ||
|
|
5437fe88b0 |
5
constants.js
Normal file
5
constants.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const COURSES_TABLE = "eg_courses";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
COURSES_TABLE
|
||||||
|
};
|
||||||
14
handler.js
14
handler.js
@@ -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
38
handlers/courses/index.js
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
13
handlers/courses/schema.js
Normal file
13
handlers/courses/schema.js
Normal 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
20
helpers.js
Normal 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
|
||||||
|
};
|
||||||
@@ -20,6 +20,17 @@ service: eg-api # NOTE: update this with your service name
|
|||||||
provider:
|
provider:
|
||||||
name: aws
|
name: aws
|
||||||
runtime: nodejs8.10
|
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
|
# you can overwrite defaults here
|
||||||
# stage: dev
|
# stage: dev
|
||||||
@@ -55,8 +66,17 @@ provider:
|
|||||||
# - exclude-me-dir/**
|
# - exclude-me-dir/**
|
||||||
|
|
||||||
functions:
|
functions:
|
||||||
hello:
|
courses:
|
||||||
handler: handler.hello
|
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
|
# The following are a few example events you can configure
|
||||||
# NOTE: Please make sure to change your handler code to work with those events
|
# NOTE: Please make sure to change your handler code to work with those events
|
||||||
@@ -102,3 +122,24 @@ functions:
|
|||||||
# NewOutput:
|
# NewOutput:
|
||||||
# Description: "Description for the output"
|
# Description: "Description for the output"
|
||||||
# Value: "Some output value"
|
# 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
|
||||||
13
services/db/dynamoClient.js
Normal file
13
services/db/dynamoClient.js
Normal 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
70
services/db/index.js
Normal 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
35
validators/index.js
Normal 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,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user