Skip to content

Commit

Permalink
Merge pull request #14 from winglang/dynamo-simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
Chriscbr authored Aug 16, 2023
2 parents 558fa3b + 0be5983 commit 7680296
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 40 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ Inspired by https://eloeverything.co/.

## Development

> **Note**
> This project can't be tested using the Wing simulator yet.
### Deployment

To deploy your own copy of the app, first make sure you have AWS credentials configured in your terminal for the account and region you want to deploy to.
Expand Down
3 changes: 3 additions & 0 deletions dynamo.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export async function _getItem(tableName, key) {

const response = await client.send(command);
console.log(response);
if (!data.Item) {
return undefined;
}
return response;
}

Expand Down
138 changes: 132 additions & 6 deletions dynamodb.w
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bring "@cdktf/provider-aws" as tfaws;
bring aws;
bring cloud;
bring util;

// --- dynamodb ---
Expand All @@ -15,23 +16,67 @@ struct Attribute {
value: Json;
}

class Util {
extern "./util.js" static inflight jsonToMutArray(json: Json): MutArray<Map<Attribute>>;
extern "./util.js" static inflight jsonToArray(json: Json): Array<Map<Attribute>>;
extern "./util.js" static inflight mutArrayToJson(json: MutArray<Map<Attribute>>): Json;
}

// TODO: https://github.com/winglang/wing/issues/3350
// typealias Item = Map<Attribute>;

struct DynamoDBTableProps {
hashKey: str;
}

class DynamoDBTable {
class DynamoDBTableSim {
key: str;
data: cloud.Bucket;

init(props: DynamoDBTableProps) {
this.key = "data.json";
this.data = new cloud.Bucket();
this.data.addObject(this.key, "[]");
}

inflight putItem(item: Map<Attribute>) {
let items = this.data.getJson(this.key);
let itemsMut = Util.jsonToMutArray(items);
itemsMut.push(item);
this.data.putJson(this.key, Util.mutArrayToJson(itemsMut));
}

inflight getItem(map: Map<Attribute>): Map<Attribute>? {
let items = this.data.getJson(this.key);
let itemsMut = Util.jsonToMutArray(items);
for item in itemsMut {
let var matches = true;
for key in map.keys() {
let attr1 = item.get(key);
let attr2 = map.get(key);
if attr1.value != attr2.value {
matches = false;
break;
}
}
if matches {
return item;
}
}
return nil;
}

inflight scan(): Array<Map<Attribute>> {
let items = this.data.getJson(this.key);
return Util.jsonToArray(items);
}
}

class DynamoDBTableAws {
table: tfaws.dynamodbTable.DynamodbTable;
tableName: str;
hashKey: str;
init(props: DynamoDBTableProps) {
let target = util.env("WING_TARGET");
if target != "tf-aws" {
throw("Unsupported target: ${target} (expected 'tf-aws')");
}

this.hashKey = props.hashKey;
this.table = new tfaws.dynamodbTable.DynamodbTable(
name: "${this.node.id}-${this.node.addr.substring(this.node.addr.length - 8)}",
Expand Down Expand Up @@ -149,3 +194,84 @@ class DynamoDBTable {
}
}
}

class DynamoDBTable {
tableSim: DynamoDBTableSim?;
tableAws: DynamoDBTableAws?;

init(props: DynamoDBTableProps) {
let target = util.env("WING_TARGET");
if target == "sim" {
this.tableSim = new DynamoDBTableSim(props);
} elif target == "tf-aws" {
this.tableAws = new DynamoDBTableAws(props);
} else {
throw("DynamoDBTable doesn't support target '${target}'");
}
}

bind(host: std.IInflightHost, ops: Array<str>) {
// currently simulator does not require permissions
// may change with https://github.com/winglang/wing/issues/3082
if let tableAws = this.tableAws {
if let host = aws.Function.from(host) {
if ops.contains("putItem") {
host.addPolicyStatements([aws.PolicyStatement {
actions: ["dynamodb:PutItem"],
resources: [tableAws.table.arn],
effect: aws.Effect.ALLOW,
}]);
}

if ops.contains("getItem") {
host.addPolicyStatements([aws.PolicyStatement {
actions: ["dynamodb:GetItem"],
resources: [tableAws.table.arn],
effect: aws.Effect.ALLOW,
}]);
}

if ops.contains("scan") {
host.addPolicyStatements([aws.PolicyStatement {
actions: ["dynamodb:Scan"],
resources: [tableAws.table.arn],
effect: aws.Effect.ALLOW,
}]);
}
}
}
}

inflight getItem(key: Map<Attribute>): Map<Attribute>? {
assert(key.size() == 1);
if let tableSim = this.tableSim {
return tableSim.getItem(key);
}
if let tableAws = this.tableAws {
return tableAws.getItem(key);
}
throw("no table instance found for getItem");
}

inflight putItem(item: Map<Attribute>) {
if let tableSim = this.tableSim {
tableSim.putItem(item);
return;
}
if let tableAws = this.tableAws {
tableAws.putItem(item);
return;
}
throw("no table instance found for putItem");
}

inflight scan(): Array<Map<Attribute>> {
if let tableSim = this.tableSim {
return tableSim.scan();
}
if let tableAws = this.tableAws {
return tableAws.scan();
}
throw("no table instance found for scan");
}
}
74 changes: 44 additions & 30 deletions main.w
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ bring "./dynamodb.w" as ddb;
bring cloud;
bring math;

// TODO: add image for each item?

// --- types ---

struct Entry {
Expand Down Expand Up @@ -66,17 +64,21 @@ class Store {
this.table.putItem(_entryToMap(entry));
}

inflight getEntry(name: str): Entry {
inflight getEntry(name: str): Entry? {
let item = this.table.getItem(Map<ddb.Attribute> {
"Name" => ddb.Attribute {
type: ddb.AttributeType.String,
value: name,
},
});
return Entry {
name: name,
score: num.fromStr(str.fromJson(item.get("Score").value)),
};
if let item = item {
return Entry {
name: name,
score: num.fromStr(str.fromJson(item.get("Score").value)),
};
} else {
return nil;
}
}

inflight getRandomPair(): Array<Entry> {
Expand All @@ -96,8 +98,20 @@ class Store {
inflight updateScores(winner: str, loser: str): Array<num> {
let entries = this.list();

let winnerScore = this.getEntry(winner).score;
let loserScore = this.getEntry(loser).score;
let winnerEntry = this.getEntry(winner);
let loserEntry = this.getEntry(loser);
let var winnerScore = 0;
let var loserScore = 0;
if let winnerEntry = winnerEntry {
winnerScore = winnerEntry.score;
} else {
throw("Winner is not a valid item");
}
if let loserEntry = loserEntry {
loserScore = loserEntry.score;
} else {
throw("Loser is not a valid item");
}

// probability that the winner should have won
let pWinner = 1.0 / (1.0 + 10 ** ((loserScore - winnerScore) / 400.0));
Expand Down Expand Up @@ -182,6 +196,12 @@ let api = new cloud.Api() as "VotingAppApi";
let website = new cloud.Website(path: "./website/build");
website.addJson("config.json", { apiUrl: api.url });

let corsHeaders = {
"Access-Control-Allow-Headers" => "*",
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "OPTIONS,GET",
};

// Select two random items from the list of items for the user to choose between
api.post("/requestChoices", inflight (_) => {
let entries = store.getRandomPair();
Expand All @@ -190,12 +210,7 @@ api.post("/requestChoices", inflight (_) => {
entryNames.push(entry.name);
}
return cloud.ApiResponse {
// TODO: refactor to a constant - https://github.com/winglang/wing/issues/3119
headers: {
"Access-Control-Allow-Headers" => "*",
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "OPTIONS,GET",
},
headers: corsHeaders,
status: 200,
body: Json.stringify(entryNames),
};
Expand All @@ -205,12 +220,7 @@ api.post("/requestChoices", inflight (_) => {
api.get("/leaderboard", inflight (_) => {
let entries = store.list();
return cloud.ApiResponse {
// TODO: refactor to a constant - https://github.com/winglang/wing/issues/3119
headers: {
"Access-Control-Allow-Headers" => "*",
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "OPTIONS,GET",
},
headers: corsHeaders,
status: 200,
body: Json.stringify(entries),
};
Expand All @@ -220,21 +230,25 @@ api.get("/leaderboard", inflight (_) => {
api.post("/selectWinner", inflight (req) => {
let body = Json.parse(req.body ?? "");
log(Json.stringify(body, 2));
// TODO: https://github.com/winglang/wing/pull/3648
let selections = Util.jsonToSelectWinnerRequest(body);

let newScores = store.updateScores(selections.winner, selections.loser);
let selections = SelectWinnerRequest.fromJson(body);

let var newScores = Array<num>[];
try {
newScores = store.updateScores(selections.winner, selections.loser);
} catch e {
return cloud.ApiResponse {
headers: corsHeaders,
status: 400,
body: "Error: " + Json.stringify(e),
};
}
let payload = SelectWinnerResponse {
winner: newScores.at(0),
loser: newScores.at(1),
};

return cloud.ApiResponse {
headers: {
"Access-Control-Allow-Headers" => "*",
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "OPTIONS,GET",
},
headers: corsHeaders,
status: 200,
body: Json.stringify(payload),
};
Expand Down
10 changes: 9 additions & 1 deletion util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
exports.jsonToSelectWinnerRequest = function(value) {
exports.jsonToMutArray = function(value) {
return value;
}

exports.jsonToArray = function(value) {
return value;
}

exports.mutArrayToJson = function(value) {
return value;
}

0 comments on commit 7680296

Please sign in to comment.