From e84d3ac4636db3bd488ecd88ba26fdef6650273b Mon Sep 17 00:00:00 2001 From: Jarrod Lowe Date: Thu, 15 Aug 2024 18:50:14 +1200 Subject: [PATCH] Add a mutation to graphql --- .codacy.yml | 4 ++ .gitignore | 3 + Makefile | 14 +++- README.md | 2 + graphql/graphql.mk | 20 ++++++ graphql/mutation/createGame/appsync.js | 37 +++++++++++ graphql/mutation/createGame/appsync.ts | 62 ++++++++++++++++++ graphql/package-lock.json | 27 ++++++++ graphql/package.json | 7 ++ graphql/schema.graphql | 10 +-- .../wildsea-dev/.terraform.lock.hcl | 38 +++++++++++ terraform/environment/wildsea-dev/plan | Bin 20576 -> 0 bytes terraform/module/iac-roles/policy.tf | 8 ++- terraform/module/wildsea/cognito.tf | 2 +- terraform/module/wildsea/graphql.tf | 59 +++++++++++++++++ 15 files changed, 281 insertions(+), 12 deletions(-) create mode 100644 .codacy.yml create mode 100644 graphql/graphql.mk create mode 100644 graphql/mutation/createGame/appsync.js create mode 100644 graphql/mutation/createGame/appsync.ts create mode 100644 graphql/package-lock.json create mode 100644 graphql/package.json delete mode 100644 terraform/environment/wildsea-dev/plan diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 00000000..28bf9da6 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,4 @@ +--- +exclude_paths: + - "graphql/mutation/*/appsync.js" + - "graphql/query/*/appsync.js" diff --git a/.gitignore b/.gitignore index 7551e3cb..eb930316 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + .validate .apply plan.tfplan + +graphql/node_modules diff --git a/Makefile b/Makefile index 1c0e8f87..b3f87132 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ RW_ROLE = arn:aws:iam::$(ACCOUNT_ID):role/GitHubAction-Wildsea-rw-dev all: $(TERRAFOM_VALIDATE) +include graphql/graphql.mk + .PHONY: terraform-format terraform-format: $(addprefix terraform-format-environment-,$(TERRAFORM_ENVIRONMENTS)) $(addprefix terraform-format-module-,$(TERRAFORM_MODULES)) @true @@ -35,13 +37,16 @@ terraform/environment/aws-dev/.apply: terraform/environment/aws-dev/*.tf terrafo ./terraform/environment/aws-dev/deploy.sh $(ACCOUNT_ID) dev touch $@ -terraform/environment/wildsea-dev/plan.tfplan: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf terraform/environment/wildsea-dev/.terraform +terraform/environment/wildsea-dev/plan.tfplan: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf terraform/environment/wildsea-dev/.terraform $(GRAPHQL) cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RO_ROLE) \ terraform plan -out=./plan.tfplan -terraform/environment/wildsea-dev/.apply: terraform/environment/wildsea-dev/plan.tfplan +terraform/environment/wildsea-dev/.apply: terraform/environment/wildsea-dev/plan.tfplan $(GRAPHQL) cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RW_ROLE) \ - terraform apply ./plan.tfplan + terraform apply ./plan.tfplan ; \ + status=$$? ; \ + rm -f $< ; \ + [ "$$status" -eq 0 ] touch $@ terraform/environment/wildsea-dev/.terraform: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf @@ -54,3 +59,6 @@ terraform/environment/wildsea-dev/.terraform: terraform/environment/wildsea-dev/ clean: rm -f terraform/environment/*/.validate rm -f terraform/environment/*/plan.tfplan + rm -f graphql/mutation/*/appsync.js + rm -f graphql/query/*/appsync.js + rm -rf graphql/node_modules diff --git a/README.md b/README.md index f7650631..78a710e9 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ If you do not set the secret, then Cognito will be used as the identity source. ## Development Environment +Install esbuild with `sudo npm install -g --save-exact --save-dev esbuild` + After having set up the AWS Account, use `AWS_PROFILE= make dev` to deploy a development version. If this is a different AWS Account from the real deployment, you will need to create an S3 bucket for the state, in the same way diff --git a/graphql/graphql.mk b/graphql/graphql.mk new file mode 100644 index 00000000..39140c2f --- /dev/null +++ b/graphql/graphql.mk @@ -0,0 +1,20 @@ +graphql/%/appsync.js: graphql/node_modules $(wildcard graphql/%/*.ts) + cd graphql && \ + esbuild $*/*.ts \ + --bundle \ + --external:"@aws-appsync/utils" \ + --format=esm \ + --platform=node \ + --target=esnext \ + --sourcemap=inline \ + --sources-content=false \ + --outdir=$* + +graphql/node_modules: graphql/package.json + cd graphql && npm install + +GRAPHQL := $(patsubst %.ts,%.js,$(wildcard graphql/*/*/appsync.ts)) + +.PHONY: graphql +graphql: $(GRAPHQL) + echo $(GRAPHQL) diff --git a/graphql/mutation/createGame/appsync.js b/graphql/mutation/createGame/appsync.js new file mode 100644 index 00000000..8741eaad --- /dev/null +++ b/graphql/mutation/createGame/appsync.js @@ -0,0 +1,37 @@ +// mutation/createGame/appsync.ts +import { util } from "@aws-appsync/utils"; +function request(ctx) { + if (!ctx.identity) { + util.error("Unauthorized: Identity information is missing."); + } + const identity = ctx.identity; + if (!identity.sub) { + util.error("Unauthorized: User ID is missing."); + } + const { input } = ctx.arguments; + const id = util.autoId(); + const timestamp = util.time.nowISO8601(); + return { + operation: "PutItem", + key: util.dynamodb.toMapValues({ PK: `GAME#${id}`, SK: "GAME" }), + attributeValues: util.dynamodb.toMapValues({ + ...input, + id, + fireflyUserId: identity.sub, + createdAt: timestamp, + updatedAt: timestamp + }) + }; +} +function response(ctx) { + const { error, result } = ctx; + if (error) { + return util.appendError(error.message, error.type, result); + } + return result; +} +export { + request, + response +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiYXBwc3luYy50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFBQSxTQUFTLFlBQXFFO0FBOEJ2RSxTQUFTLFFBQVEsS0FBa0U7QUFDdEYsTUFBSSxDQUFDLElBQUksVUFBVTtBQUNmLFNBQUssTUFBTSxnREFBZ0Q7QUFBQSxFQUMvRDtBQUVBLFFBQU0sV0FBVyxJQUFJO0FBQ3JCLE1BQUksQ0FBQyxTQUFTLEtBQUs7QUFDZixTQUFLLE1BQU0sbUNBQW1DO0FBQUEsRUFDbEQ7QUFFQSxRQUFNLEVBQUUsTUFBTSxJQUFJLElBQUk7QUFDdEIsUUFBTSxLQUFLLEtBQUssT0FBTztBQUN2QixRQUFNLFlBQVksS0FBSyxLQUFLLFdBQVc7QUFDdkMsU0FBTztBQUFBLElBQ0gsV0FBVztBQUFBLElBQ1gsS0FBSyxLQUFLLFNBQVMsWUFBWSxFQUFFLElBQUksUUFBUSxFQUFFLElBQUksSUFBSSxPQUFPLENBQUM7QUFBQSxJQUMvRCxpQkFBaUIsS0FBSyxTQUFTLFlBQVk7QUFBQSxNQUN2QyxHQUFHO0FBQUEsTUFDSDtBQUFBLE1BQ0EsZUFBZSxTQUFTO0FBQUEsTUFDeEIsV0FBVztBQUFBLE1BQ1gsV0FBVztBQUFBLElBQ2YsQ0FBQztBQUFBLEVBQ0w7QUFDSjtBQUVPLFNBQVMsU0FBUyxLQUF1QjtBQUM1QyxRQUFNLEVBQUUsT0FBTyxPQUFPLElBQUk7QUFDMUIsTUFBSSxPQUFPO0FBQ1AsV0FBTyxLQUFLLFlBQVksTUFBTSxTQUFTLE1BQU0sTUFBTSxNQUFNO0FBQUEsRUFDN0Q7QUFDQSxTQUFPO0FBQ1g7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/graphql/mutation/createGame/appsync.ts b/graphql/mutation/createGame/appsync.ts new file mode 100644 index 00000000..d464b1e9 --- /dev/null +++ b/graphql/mutation/createGame/appsync.ts @@ -0,0 +1,62 @@ +import { util, Context, DynamoDBPutItemRequest, AppSyncIdentityCognito, PutItemInputAttributeMap } from '@aws-appsync/utils'; + +/** + * A CreateGameInput creates a Game. + * They are stored in DynamoDB with a PK of `GAME#` and an SK of `GAME`. + * The ID is a UUID + * The fireflyUserId is the Cognito ID of the user + +input CreateGameInput { + name: String! + description: String +} + +type Game { + id: ID! + name: String! + description: String + publicNotes: String + privateNotes: String + fireflyUserId: ID! + createdAt: AWSDateTime! + updatedAt: AWSDateTime! +} + */ + +interface CreateGameInput { + name: string; + description?: string; +} + +export function request(ctx: Context<{ input: CreateGameInput }>): DynamoDBPutItemRequest { + if (!ctx.identity) { + util.error('Unauthorized: Identity information is missing.'); + } + + var identity = ctx.identity as AppSyncIdentityCognito; + if (!identity.sub) { + util.error('Unauthorized: User ID is missing.'); + } + + var input = ctx.arguments.input; + var id = util.autoId(); + var timestamp = util.time.nowISO8601(); + return { + operation: 'PutItem', + key: util.dynamodb.toMapValues({ PK: `GAME#${id}`, SK: 'GAME' }), + attributeValues: util.dynamodb.toMapValues({ + ...input, + id, + fireflyUserId: identity.sub, + createdAt: timestamp, + updatedAt: timestamp, + }) as PutItemInputAttributeMap, + }; +} + +export function response(ctx: Context): unknown { + if (ctx.error) { + return util.appendError(ctx.error.message, ctx.error.type, ctx.result); + } + return ctx.result; +} diff --git a/graphql/package-lock.json b/graphql/package-lock.json new file mode 100644 index 00000000..d45b4c7b --- /dev/null +++ b/graphql/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "graphql", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "graphql", + "version": "1.0.0", + "dependencies": { + "@aws-appsync/utils": "^1.7.0" + } + }, + "node_modules/@aws-appsync/utils": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@aws-appsync/utils/-/utils-1.9.0.tgz", + "integrity": "sha512-TAZNHiMpJKafrur6sE0Ou48gbuRL1oJXUDfrnBbYAXDVF3+mzD/0QCRp91F/dcAUbbNkFaq8pAwLEBTmpvmm9g==" + } + }, + "dependencies": { + "@aws-appsync/utils": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@aws-appsync/utils/-/utils-1.9.0.tgz", + "integrity": "sha512-TAZNHiMpJKafrur6sE0Ou48gbuRL1oJXUDfrnBbYAXDVF3+mzD/0QCRp91F/dcAUbbNkFaq8pAwLEBTmpvmm9g==" + } + } +} diff --git a/graphql/package.json b/graphql/package.json new file mode 100644 index 00000000..fa3dfb01 --- /dev/null +++ b/graphql/package.json @@ -0,0 +1,7 @@ +{ + "name": "graphql", + "version": "1.0.0", + "dependencies": { + "@aws-appsync/utils": "^1.7.0" + } +} diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 16067205..bfb4a720 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -1,21 +1,17 @@ type Mutation { - createGame(input: CreateGameInput!): Game! + createGame(input: CreateGameInput!): Game! @aws_cognito_user_pools } type Query { - getGame(input: ID!): Game! + getGame(input: ID!): Game! @aws_cognito_user_pools } input CreateGameInput { name: String! description: String - publicNotes: String - privateNotes: String - fireflyUserId: ID! - players: [ID!]! } -type Game { +type Game @aws_cognito_user_pools { id: ID! name: String! description: String diff --git a/terraform/environment/wildsea-dev/.terraform.lock.hcl b/terraform/environment/wildsea-dev/.terraform.lock.hcl index 169a35a3..4360bd0c 100644 --- a/terraform/environment/wildsea-dev/.terraform.lock.hcl +++ b/terraform/environment/wildsea-dev/.terraform.lock.hcl @@ -22,3 +22,41 @@ provider "registry.terraform.io/hashicorp/aws" { "zh:ffb40a66b4d000a8ee4c54227eeb998f887ad867419c3af7d3981587788de074", ] } + +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.1" + hashes = [ + "h1:8oTPe2VUL6E2d3OcrvqyjI4Nn/Y/UEQN26WLk5O/B0g=", + "zh:0af29ce2b7b5712319bf6424cb58d13b852bf9a777011a545fac99c7fdcdf561", + "zh:126063ea0d79dad1f68fa4e4d556793c0108ce278034f101d1dbbb2463924561", + "zh:196bfb49086f22fd4db46033e01655b0e5e036a5582d250412cc690fa7995de5", + "zh:37c92ec084d059d37d6cffdb683ccf68e3a5f8d2eb69dd73c8e43ad003ef8d24", + "zh:4269f01a98513651ad66763c16b268f4c2da76cc892ccfd54b401fff6cc11667", + "zh:51904350b9c728f963eef0c28f1d43e73d010333133eb7f30999a8fb6a0cc3d8", + "zh:73a66611359b83d0c3fcba2984610273f7954002febb8a57242bbb86d967b635", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7ae387993a92bcc379063229b3cce8af7eaf082dd9306598fcd42352994d2de0", + "zh:9e0f365f807b088646db6e4a8d4b188129d9ebdbcf2568c8ab33bddd1b82c867", + "zh:b5263acbd8ae51c9cbffa79743fbcadcb7908057c87eb22fd9048268056efbc4", + "zh:dfcd88ac5f13c0d04e24be00b686d069b4879cc4add1b7b1a8ae545783d97520", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.2" + hashes = [ + "h1:zT1ZbegaAYHwQa+QwIFugArWikRJI9dqohj8xb0GY88=", + "zh:3248aae6a2198f3ec8394218d05bd5e42be59f43a3a7c0b71c66ec0df08b69e7", + "zh:32b1aaa1c3013d33c245493f4a65465eab9436b454d250102729321a44c8ab9a", + "zh:38eff7e470acb48f66380a73a5c7cdd76cc9b9c9ba9a7249c7991488abe22fe3", + "zh:4c2f1faee67af104f5f9e711c4574ff4d298afaa8a420680b0cb55d7bbc65606", + "zh:544b33b757c0b954dbb87db83a5ad921edd61f02f1dc86c6186a5ea86465b546", + "zh:696cf785090e1e8cf1587499516b0494f47413b43cb99877ad97f5d0de3dc539", + "zh:6e301f34757b5d265ae44467d95306d61bef5e41930be1365f5a8dcf80f59452", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:913a929070c819e59e94bb37a2a253c228f83921136ff4a7aa1a178c7cce5422", + "zh:aa9015926cd152425dbf86d1abdbc74bfe0e1ba3d26b3db35051d7b9ca9f72ae", + "zh:bb04798b016e1e1d49bcc76d62c53b56c88c63d6f2dfe38821afef17c416a0e1", + "zh:c23084e1b23577de22603cff752e59128d83cfecc2e6819edadd8cf7a10af11e", + ] +} diff --git a/terraform/environment/wildsea-dev/plan b/terraform/environment/wildsea-dev/plan deleted file mode 100644 index 43dadce6d187308d3ccc29cf1314a4f517378156..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20576 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 diff --git a/terraform/module/iac-roles/policy.tf b/terraform/module/iac-roles/policy.tf index e1c8ffd7..bff2a868 100644 --- a/terraform/module/iac-roles/policy.tf +++ b/terraform/module/iac-roles/policy.tf @@ -46,9 +46,9 @@ data "aws_iam_policy_document" "ro" { sid = "CognitoIdpGlobal" actions = [ "cognito-idp:DescribeUserPoolDomain", - "appsync:SetWebACL", "wafv2:GetWebACLForResource", "wafv2:GetWebAcl", + "appsync:GetResolver", ] resources = [ "*" @@ -194,6 +194,9 @@ data "aws_iam_policy_document" "rw" { "cognito-identity:SetIdentityPoolRoles", "cognito-identity:TagResource", "cognito-identity:UntagResource", + "appsync:CreateResolver", + "appsync:DeleteResolver", + "appsync:SetWebACL", ] resources = [ "*" @@ -389,6 +392,9 @@ data "aws_iam_policy_document" "rw_boundary" { "appsync:SetWebACL", "wafv2:GetWebACLForResource", "wafv2:GetWebAcl", + "appsync:CreateResolver", + "appsync:DeleteResolver", + "appsync:GetResolver", ] resources = [ "*" diff --git a/terraform/module/wildsea/cognito.tf b/terraform/module/wildsea/cognito.tf index bc16d35f..d4166544 100644 --- a/terraform/module/wildsea/cognito.tf +++ b/terraform/module/wildsea/cognito.tf @@ -27,7 +27,7 @@ resource "aws_cognito_identity_provider" "idp" { resource "aws_cognito_user_pool_client" "cognito" { name = var.prefix user_pool_id = aws_cognito_user_pool.cognito.id - generate_secret = true + generate_secret = false explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_PASSWORD_AUTH", "ALLOW_USER_SRP_AUTH"] allowed_oauth_flows_user_pool_client = true callback_urls = ["https://TODO"] diff --git a/terraform/module/wildsea/graphql.tf b/terraform/module/wildsea/graphql.tf index 0ff576bc..cef72559 100644 --- a/terraform/module/wildsea/graphql.tf +++ b/terraform/module/wildsea/graphql.tf @@ -226,3 +226,62 @@ resource "aws_iam_role_policy_attachment" "graphql_datasource" { role = aws_iam_role.graphql_datasource.name policy_arn = aws_iam_policy.graphql_datasource.arn } + +locals { + mutations = distinct([for d in fileset("${path.module}/../../../graphql/mutation", "**") : dirname(d)]) + queries = distinct([for d in fileset("${path.module}/../../../graphql/query", "**") : dirname(d)]) + + mutations_map = { + for mutation in local.mutations : replace(mutation, "../../../graphql/mutation/", "") => { + "type" : "Mutation", + "path" : "../../../graphql/mutation/${mutation}/appsync.js", + "make" : "graphql/mutation/${mutation}/appsync.js", + "source" : "../../../graphql/mutation/${mutation}/appsync.ts" + } + } + + queries_map = { + for query in local.queries : replace(query, "../../../graphql/query/", "") => { + "type" : "Query", + "path" : "../../../graphql/query/${query}/appsync.js", + "make" : "graphql/query/${query}/appsync.js", + "source" : "../../../graphql/query/${query}/appsync.ts" + } + } + + all = merge(local.mutations_map, local.queries_map) +} + +resource "aws_appsync_resolver" "resolver" { + for_each = local.all + + api_id = aws_appsync_graphql_api.graphql.id + type = each.value.type + field = each.key + data_source = aws_appsync_datasource.graphql.name + code = data.local_file.graphql_code[each.key].content + + runtime { + name = "APPSYNC_JS" + runtime_version = "1.0.0" + } +} + +resource "null_resource" "graphql_compile" { + for_each = local.all + + provisioner "local-exec" { + command = "cd ${path.module}/../../.. && make ${each.value.make}" + } + + triggers = { + source_change = filesha256(each.value.source) + dest_file = each.value.path + } +} + +data "local_file" "graphql_code" { + for_each = local.all + + filename = null_resource.graphql_compile[each.key].triggers.dest_file +}