-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
586 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,7 @@ | ||
extends: airbnb | ||
rules: | ||
func-names: off | ||
import/no-extraneous-dependencies: off | ||
object-shorthand: off | ||
require-yield: off | ||
strict: off |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
/.s3-website.json | ||
/.serverless | ||
/app/config.json | ||
/elm-stuff/ | ||
/node_modules/ | ||
/npm-debug.log | ||
/public/ | ||
/env.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# package directories | ||
elm-stuff | ||
node_modules | ||
jspm_packages | ||
|
||
# Serverless directories | ||
.serverless |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
'use strict'; | ||
|
||
const AWS = require('aws-sdk'); | ||
const base64 = require('base64'); // eslint-disable-line import/no-unresolved | ||
const BPromise = require('bluebird'); | ||
const genId = require('./gen-id'); | ||
const router = require('koa-router'); | ||
|
||
AWS.config.setPromisesDependency(BPromise); | ||
const dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10' }); | ||
const api = module.exports = router({ prefix: '/challenge' }); | ||
const table = 'wordsnake_challenge'; | ||
|
||
const toChallenge = data => ({ | ||
id: data.Item.id.S, | ||
board: data.Item.board.S, | ||
shape: data.Item.shape.S, | ||
results: data.Item.results.L.map(result => ({ | ||
player: result.M.player.S, | ||
score: parseInt(result.M.score.N, 10), | ||
words: result.M.words.SS, | ||
})), | ||
}); | ||
|
||
const getById = id => dynamodb.getItem({ | ||
TableName: table, | ||
Key: { id: { S: id } }, | ||
}).promise().then(toChallenge); | ||
|
||
api.get('/:id', function* () { | ||
this.body = yield getById(this.params.id); | ||
}); | ||
|
||
const validateResult = (context) => { | ||
const player = context.request.body.player; | ||
const score = context.request.body.score; | ||
const encodedToken = context.request.body.token; | ||
if (!player || !score || !encodedToken) { | ||
context.throw(400, 'Missing parameter'); | ||
return null; | ||
} | ||
let token; | ||
try { | ||
token = JSON.parse(base64.atob(encodedToken)); | ||
} catch (err) { | ||
context.throw(400, 'Invalid token encoding'); | ||
return null; | ||
} | ||
const board = token.b; | ||
const shape = token.s; | ||
const words = token.w; | ||
if (!board || !shape || !words | ||
|| !/^[A-Z]{9,}$/.test(board) | ||
|| !/^(\d+)x(\d+)/.test(shape) | ||
|| !/^(([A-Z]{3,})(,[A-Z]{3,})*)?$/.test(words) | ||
) { | ||
context.throw(400, 'Invalid token'); | ||
return null; | ||
} | ||
return { | ||
board: board, | ||
shape: shape, | ||
result: { | ||
M: { | ||
player: { S: player.toString() }, | ||
score: { N: parseInt(score.toString(), 10).toString() }, | ||
words: { SS: words.split(',') }, | ||
}, | ||
}, | ||
}; | ||
}; | ||
|
||
api.post('/', function* () { | ||
const id = genId(); | ||
const v = validateResult(this); | ||
if (v) { | ||
this.body = yield dynamodb.putItem({ | ||
TableName: table, | ||
Item: { | ||
id: { S: id }, | ||
board: { S: v.board }, | ||
shape: { S: v.shape }, | ||
results: { L: [v.result] }, | ||
}, | ||
}).promise().then(() => getById(id)); | ||
} | ||
}); | ||
|
||
api.post('/:id', function* () { | ||
const id = this.params.id; | ||
const v = validateResult(this); | ||
this.body = yield dynamodb.updateItem({ | ||
TableName: table, | ||
Key: { id: { S: id } }, | ||
AttributeUpdates: { | ||
results: { | ||
Action: 'ADD', | ||
Value: { L: [v.result] }, | ||
}, | ||
}, | ||
}).promise().then(() => getById(id)); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
'use strict'; | ||
|
||
const rc = require('rc'); | ||
|
||
const conf = rc('wordsnake', { | ||
stage: 'dev', | ||
}); | ||
|
||
module.exports = conf; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const randomstring = require('randomstring'); | ||
|
||
// 57 ** 7 = 1,954,897,493,193 | ||
const genId = () => randomstring.generate({ | ||
length: 7, | ||
charset: 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', | ||
}); | ||
|
||
module.exports = genId; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use strict'; | ||
|
||
const bodyParser = require('koa-bodyparser'); | ||
const koa = require('koa'); | ||
const cors = require('koa-cors'); | ||
const logger = require('./logger'); | ||
const logging = require('@microservice/koa-logging'); | ||
const middleware = require('./middleware'); | ||
const router = require('koa-router'); | ||
const serverless = require('serverless-http'); | ||
const challenge = require('./challenge'); | ||
|
||
const routes = router(); | ||
routes.use(middleware.error); | ||
routes.use(logging(logger)); | ||
routes.use(bodyParser()); | ||
|
||
routes.use(challenge.routes()); | ||
routes.use(challenge.allowedMethods()); | ||
|
||
routes.get('/', function* () { | ||
this.body = 'WordSnake API'; | ||
}); | ||
|
||
module.exports.routes = routes; | ||
|
||
const app = koa(); | ||
app.use(cors({ | ||
origin: true, | ||
credentials: true, | ||
methods: ['GET', 'HEAD', 'OPTIONS'], | ||
})); | ||
app.use(routes.routes()); | ||
app.use(routes.allowedMethods()); | ||
module.exports.handler = serverless(app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
'use strict'; | ||
|
||
const conf = require('./conf'); | ||
|
||
module.exports = require('@microservice/logger')({ | ||
name: 'wordsnake', | ||
environment: conf.stage, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
'use strict'; | ||
|
||
const m = module.exports = {}; | ||
|
||
m.error = function* (next) { | ||
try { | ||
yield next; | ||
} catch (err) { | ||
this.status = err.status || err.statusCode || 500; | ||
this.body = err.cause || (err.message ? { message: err.message } : err); | ||
this.app.emit('error', err, this); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
module Challenge.Challenge exposing (..) | ||
|
||
import Http | ||
import Json.Decode as D exposing (at) | ||
import Json.Encode as J | ||
import Regex exposing (regex, HowMany(..)) | ||
import Routing.Shape as Shape exposing (..) | ||
import Task exposing (Task) | ||
import Routing.Token as Token exposing (..) | ||
import UrlParser exposing (..) | ||
|
||
|
||
type alias Endpoint = | ||
String | ||
|
||
|
||
type alias Challenge = | ||
{ id : Id | ||
, board : String | ||
, shape : Shape | ||
, results : List GameResult | ||
} | ||
|
||
|
||
type alias Id = | ||
String | ||
|
||
|
||
type alias GameResult = | ||
{ player : String | ||
, score : Int | ||
, words : List String | ||
} | ||
|
||
|
||
type Error | ||
= HttpError Http.Error | ||
| Error String | ||
|
||
|
||
idParser : Parser (Id -> a) a | ||
idParser = | ||
UrlParser.custom "CHALLENGE_ID" | ||
(\segment -> | ||
case parseId segment of | ||
Just id -> | ||
Ok id | ||
|
||
Nothing -> | ||
Err ("Bad id: " ++ segment) | ||
) | ||
|
||
|
||
parseId : String -> Maybe String | ||
parseId w = | ||
w | ||
|> Regex.find (AtMost 1) (regex "^([0-9A-Za-z]{7})$") | ||
|> List.head | ||
|> Maybe.andThen | ||
(\m -> | ||
m.submatches | ||
|> List.head | ||
|> Maybe.andThen (\id -> id) | ||
) | ||
|
||
|
||
|
||
-- API | ||
|
||
|
||
get : Endpoint -> Id -> Task Error Challenge | ||
get endpoint id = | ||
Http.get (endpoint ++ "/challenge/" ++ id) challengeDecoder | ||
|> Http.toTask | ||
|> Task.mapError HttpError | ||
|
||
|
||
create : Endpoint -> ( String, Int, Token ) -> Task Error Challenge | ||
create endpoint gameResult = | ||
Http.request | ||
{ method = "POST" | ||
, headers = [] | ||
, url = endpoint ++ "/challenge" | ||
, body = Http.jsonBody (gameResult |> gameResultEncoder) | ||
, expect = Http.expectJson challengeDecoder | ||
, timeout = Nothing | ||
, withCredentials = False | ||
} | ||
|> Http.toTask | ||
|> Task.mapError HttpError | ||
|
||
|
||
update : Endpoint -> Id -> ( String, Int, Token ) -> Task Error Challenge | ||
update endpoint id gameResult = | ||
Task.fail (Error "not implemented") | ||
|
||
|
||
|
||
-- ENCODER | ||
|
||
|
||
gameResultEncoder : ( String, Int, Token ) -> J.Value | ||
gameResultEncoder ( player, score, token ) = | ||
J.object | ||
[ ( "player", J.string player ) | ||
, ( "score", J.int score ) | ||
, ( "token", token |> Token.toPathComponent |> J.string ) | ||
] | ||
|
||
|
||
|
||
-- DECODER | ||
|
||
|
||
challengeDecoder : D.Decoder Challenge | ||
challengeDecoder = | ||
D.map4 Challenge | ||
(at [ "id" ] D.string) | ||
(at [ "board" ] D.string) | ||
(at [ "shape" ] Shape.decoder) | ||
(at [ "results" ] (D.list gameResultDecoder)) | ||
|
||
|
||
gameResultDecoder : D.Decoder GameResult | ||
gameResultDecoder = | ||
D.map3 GameResult | ||
(at [ "player" ] D.string) | ||
(at [ "score" ] D.int) | ||
(at [ "words" ] (D.list D.string)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.