├── .gitignore ├── .graphqlconfig.yml ├── README.md ├── amplify ├── .config │ └── project-config.json └── backend │ ├── api │ └── gqls3test │ │ ├── parameters.json │ │ ├── schema.graphql │ │ └── stacks │ │ └── CustomResources.json │ ├── auth │ └── gqls3testeb3f615e │ │ ├── gqls3testeb3f615e-cloudformation-template.yml │ │ └── parameters.json │ ├── backend-config.json │ └── storage │ └── gqls3test │ ├── parameters.json │ └── s3-cloudformation-template.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── graphql │ ├── mutations.js │ ├── queries.js │ ├── schema.json │ └── subscriptions.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | #amplify 26 | amplify/team-provider-info.json 27 | amplify/\#current-cloud-backend 28 | amplify/.config/local-* 29 | amplify/backend/amplify-meta.json 30 | amplify/backend/awscloudformation 31 | build/ 32 | dist/ 33 | node_modules/ 34 | aws-exports.js 35 | awsconfiguration.json -------------------------------------------------------------------------------- /.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | gqls3test: 3 | schemaPath: src/graphql/schema.json 4 | includes: 5 | - src/graphql/**/*.js 6 | excludes: 7 | - ./amplify/** 8 | extensions: 9 | amplify: 10 | codeGenTarget: javascript 11 | generatedFileName: '' 12 | docsFilePath: src/graphql 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Image uploads and downloads with React, AWS Amplify, AWS AppSync, and Amazon S3 2 | 3 | This is an example project showing how to upload and download images from S3 using AWS Amplify, AWS AppSync, and Amazon S3 4 | 5 | ### The question 6 | 7 | How do I securely upload images using GraphQL with AWS AppSync? 8 | 9 | ### The solution 10 | 11 | There are a few parts to this solution: 12 | 13 | * You must first upload the image to a storage solution (Amazon S3) 14 | * After you have finished uploading the image, you will then be given a `key` to reference this image. You then need to store this reference in a database using a GraphQL mutation. 15 | * When you want to view this image, you need to do two things: 16 | * First, query the image reference from your database using GraphQL 17 | * Get a signed URL for the image from S3 18 | 19 | In this example, I show how to: 20 | 21 | 1. Store images using GraphQL, AppSync, and S3 22 | 2. Fetch a list of images and render them in a React application 23 | 24 | > To view the main code for this app, open [src/App.js](https://github.com/dabit3/react-amplify-appsync-s3/blob/master/src/App.js) 25 | 26 | ## To deploy this app 27 | 28 | 1. Clone the project and change into the directory 29 | 30 | ```sh 31 | git clone https://github.com/dabit3/react-amplify-appsync-s3.git 32 | 33 | cd react-amplify-appsync-s3 34 | ``` 35 | 36 | 2. Install the dependencies 37 | 38 | ```sh 39 | npm install 40 | 41 | # or 42 | 43 | yarn 44 | ``` 45 | 46 | 3. Initialize and deploy the amplify project 47 | 48 | ```sh 49 | amplify init 50 | 51 | amplify push 52 | ``` 53 | 54 | 4. Run the app 55 | 56 | ```sh 57 | npm start 58 | ``` -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "gqls3test", 3 | "version": "2.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/backend/api/gqls3test/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "gqls3test", 3 | "DynamoDBBillingMode": "PAY_PER_REQUEST", 4 | "AuthCognitoUserPoolId": { 5 | "Fn::GetAtt": [ 6 | "authgqls3testeb3f615e", 7 | "Outputs.UserPoolId" 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /amplify/backend/api/gqls3test/schema.graphql: -------------------------------------------------------------------------------- 1 | type User @model { 2 | id: ID! 3 | username: String 4 | visibility: Visibility 5 | avatar: S3Object 6 | } 7 | 8 | type S3Object { 9 | bucket: String! 10 | region: String! 11 | key: String! 12 | } 13 | 14 | enum Visibility { 15 | public 16 | private 17 | } -------------------------------------------------------------------------------- /amplify/backend/api/gqls3test/stacks/CustomResources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiId": { 7 | "Type": "String", 8 | "Description": "The id of the AppSync API associated with this project." 9 | }, 10 | "AppSyncApiName": { 11 | "Type": "String", 12 | "Description": "The name of the AppSync API", 13 | "Default": "AppSyncSimpleTransform" 14 | }, 15 | "env": { 16 | "Type": "String", 17 | "Description": "The environment name. e.g. Dev, Test, or Production", 18 | "Default": "NONE" 19 | }, 20 | "S3DeploymentBucket": { 21 | "Type": "String", 22 | "Description": "The S3 bucket containing all deployment assets for the project." 23 | }, 24 | "S3DeploymentRootKey": { 25 | "Type": "String", 26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory." 27 | } 28 | }, 29 | "Resources": { 30 | "EmptyResource": { 31 | "Type": "Custom::EmptyResource", 32 | "Condition": "AlwaysFalse" 33 | } 34 | }, 35 | "Conditions": { 36 | "HasEnvironmentParameter": { 37 | "Fn::Not": [ 38 | { 39 | "Fn::Equals": [ 40 | { 41 | "Ref": "env" 42 | }, 43 | "NONE" 44 | ] 45 | } 46 | ] 47 | }, 48 | "AlwaysFalse": { 49 | "Fn::Equals": [ 50 | "true", 51 | "false" 52 | ] 53 | } 54 | }, 55 | "Outputs": { 56 | "EmptyOutput": { 57 | "Description": "An empty output. You may delete this if you have at least one resource above.", 58 | "Value": "" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /amplify/backend/auth/gqls3testeb3f615e/gqls3testeb3f615e-cloudformation-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | 3 | Parameters: 4 | env: 5 | Type: String 6 | authRoleName: 7 | Type: String 8 | unauthRoleName: 9 | Type: String 10 | authRoleArn: 11 | Type: String 12 | unauthRoleArn: 13 | Type: String 14 | 15 | 16 | identityPoolName: 17 | Type: String 18 | 19 | allowUnauthenticatedIdentities: 20 | Type: String 21 | 22 | lambdaLogPolicy: 23 | Type: String 24 | 25 | openIdLambdaRoleName: 26 | Type: String 27 | 28 | openIdRolePolicy: 29 | Type: String 30 | 31 | openIdLambdaIAMPolicy: 32 | Type: String 33 | 34 | openIdLogPolicy: 35 | Type: String 36 | 37 | userPoolName: 38 | Type: String 39 | 40 | autoVerifiedAttributes: 41 | Type: CommaDelimitedList 42 | 43 | mfaConfiguration: 44 | Type: String 45 | 46 | mfaTypes: 47 | Type: CommaDelimitedList 48 | 49 | roleName: 50 | Type: String 51 | 52 | roleExternalId: 53 | Type: String 54 | 55 | policyName: 56 | Type: String 57 | 58 | smsAuthenticationMessage: 59 | Type: String 60 | 61 | smsVerificationMessage: 62 | Type: String 63 | 64 | emailVerificationSubject: 65 | Type: String 66 | 67 | emailVerificationMessage: 68 | Type: String 69 | 70 | defaultPasswordPolicy: 71 | Type: String 72 | 73 | passwordPolicyMinLength: 74 | Type: Number 75 | 76 | passwordPolicyCharacters: 77 | Type: CommaDelimitedList 78 | 79 | requiredAttributes: 80 | Type: CommaDelimitedList 81 | 82 | userpoolClientName: 83 | Type: String 84 | 85 | userpoolClientGenerateSecret: 86 | Type: String 87 | 88 | userpoolClientRefreshTokenValidity: 89 | Type: Number 90 | 91 | userpoolClientWriteAttributes: 92 | Type: CommaDelimitedList 93 | 94 | userpoolClientReadAttributes: 95 | Type: CommaDelimitedList 96 | 97 | mfaLambdaRole: 98 | Type: String 99 | 100 | mfaLambdaLogPolicy: 101 | Type: String 102 | 103 | mfaPassRolePolicy: 104 | Type: String 105 | 106 | mfaLambdaIAMPolicy: 107 | Type: String 108 | 109 | userpoolClientLambdaRole: 110 | Type: String 111 | 112 | userpoolClientLogPolicy: 113 | Type: String 114 | 115 | userpoolClientLambdaPolicy: 116 | Type: String 117 | 118 | userpoolClientSetAttributes: 119 | Type: String 120 | 121 | resourceName: 122 | Type: String 123 | 124 | authSelections: 125 | Type: String 126 | 127 | useDefault: 128 | Type: String 129 | 130 | Conditions: 131 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] 132 | 133 | Resources: 134 | 135 | # BEGIN SNS ROLE RESOURCE 136 | SNSRole: 137 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process 138 | Type: AWS::IAM::Role 139 | Properties: 140 | RoleName: !If [ShouldNotCreateEnvResources, !Ref roleName, !Join ['',[!Ref roleName, '-', !Ref env]]] 141 | AssumeRolePolicyDocument: 142 | Version: "2012-10-17" 143 | Statement: 144 | - Sid: "" 145 | Effect: "Allow" 146 | Principal: 147 | Service: "cognito-idp.amazonaws.com" 148 | Action: 149 | - "sts:AssumeRole" 150 | Condition: 151 | StringEquals: 152 | sts:ExternalId: !Ref roleExternalId 153 | Policies: 154 | - 155 | PolicyName: !Ref policyName 156 | PolicyDocument: 157 | Version: "2012-10-17" 158 | Statement: 159 | - 160 | Effect: "Allow" 161 | Action: 162 | - "sns:Publish" 163 | Resource: "*" 164 | # BEGIN USER POOL RESOURCES 165 | UserPool: 166 | # Created upon user selection 167 | # Depends on SNS Role for Arn if MFA is enabled 168 | Type: AWS::Cognito::UserPool 169 | UpdateReplacePolicy: Retain 170 | Properties: 171 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] 172 | 173 | Schema: 174 | 175 | - 176 | Name: email 177 | Required: true 178 | Mutable: true 179 | 180 | 181 | 182 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes 183 | 184 | 185 | EmailVerificationMessage: !Ref emailVerificationMessage 186 | EmailVerificationSubject: !Ref emailVerificationSubject 187 | 188 | Policies: 189 | PasswordPolicy: 190 | MinimumLength: !Ref passwordPolicyMinLength 191 | RequireLowercase: false 192 | RequireNumbers: false 193 | RequireSymbols: false 194 | RequireUppercase: false 195 | 196 | MfaConfiguration: !Ref mfaConfiguration 197 | SmsVerificationMessage: !Ref smsVerificationMessage 198 | SmsConfiguration: 199 | SnsCallerArn: !GetAtt SNSRole.Arn 200 | ExternalId: !Ref roleExternalId 201 | 202 | UserPoolClientWeb: 203 | # Created provide application access to user pool 204 | # Depends on UserPool for ID reference 205 | Type: "AWS::Cognito::UserPoolClient" 206 | Properties: 207 | ClientName: gqls3teb3f615e_app_clientWeb 208 | 209 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 210 | UserPoolId: !Ref UserPool 211 | DependsOn: UserPool 212 | UserPoolClient: 213 | # Created provide application access to user pool 214 | # Depends on UserPool for ID reference 215 | Type: "AWS::Cognito::UserPoolClient" 216 | Properties: 217 | ClientName: !Ref userpoolClientName 218 | 219 | GenerateSecret: !Ref userpoolClientGenerateSecret 220 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 221 | UserPoolId: !Ref UserPool 222 | DependsOn: UserPool 223 | # BEGIN USER POOL LAMBDA RESOURCES 224 | UserPoolClientRole: 225 | # Created to execute Lambda which gets userpool app client config values 226 | Type: 'AWS::IAM::Role' 227 | Properties: 228 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 229 | AssumeRolePolicyDocument: 230 | Version: '2012-10-17' 231 | Statement: 232 | - Effect: Allow 233 | Principal: 234 | Service: 235 | - lambda.amazonaws.com 236 | Action: 237 | - 'sts:AssumeRole' 238 | DependsOn: UserPoolClient 239 | UserPoolClientLambda: 240 | # Lambda which gets userpool app client config values 241 | # Depends on UserPool for id 242 | # Depends on UserPoolClientRole for role ARN 243 | Type: 'AWS::Lambda::Function' 244 | Properties: 245 | Code: 246 | ZipFile: !Join 247 | - |+ 248 | - - 'const response = require(''cfn-response'');' 249 | - 'const aws = require(''aws-sdk'');' 250 | - 'const identity = new aws.CognitoIdentityServiceProvider();' 251 | - 'exports.handler = (event, context, callback) => {' 252 | - ' if (event.RequestType == ''Delete'') { ' 253 | - ' response.send(event, context, response.SUCCESS, {})' 254 | - ' }' 255 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' 256 | - ' const params = {' 257 | - ' ClientId: event.ResourceProperties.clientId,' 258 | - ' UserPoolId: event.ResourceProperties.userpoolId' 259 | - ' };' 260 | - ' identity.describeUserPoolClient(params).promise()' 261 | - ' .then((res) => {' 262 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' 263 | - ' })' 264 | - ' .catch((err) => {' 265 | - ' response.send(event, context, response.FAILED, {err});' 266 | - ' });' 267 | - ' }' 268 | - '};' 269 | Handler: index.handler 270 | Runtime: nodejs8.10 271 | Timeout: '300' 272 | Role: !GetAtt 273 | - UserPoolClientRole 274 | - Arn 275 | DependsOn: UserPoolClientRole 276 | UserPoolClientLambdaPolicy: 277 | # Sets userpool policy for the role that executes the Userpool Client Lambda 278 | # Depends on UserPool for Arn 279 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing 280 | Type: 'AWS::IAM::Policy' 281 | Properties: 282 | PolicyName: !Ref userpoolClientLambdaPolicy 283 | Roles: 284 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 285 | PolicyDocument: 286 | Version: '2012-10-17' 287 | Statement: 288 | - Effect: Allow 289 | Action: 290 | - 'cognito-idp:DescribeUserPoolClient' 291 | Resource: !GetAtt UserPool.Arn 292 | DependsOn: UserPoolClientLambda 293 | UserPoolClientLogPolicy: 294 | # Sets log policy for the role that executes the Userpool Client Lambda 295 | # Depends on UserPool for Arn 296 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 297 | Type: 'AWS::IAM::Policy' 298 | Properties: 299 | PolicyName: !Ref userpoolClientLogPolicy 300 | Roles: 301 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 302 | PolicyDocument: 303 | Version: 2012-10-17 304 | Statement: 305 | - Effect: Allow 306 | Action: 307 | - 'logs:CreateLogGroup' 308 | - 'logs:CreateLogStream' 309 | - 'logs:PutLogEvents' 310 | Resource: !Sub 311 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* 312 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} 313 | DependsOn: UserPoolClientLambdaPolicy 314 | UserPoolClientInputs: 315 | # Values passed to Userpool client Lambda 316 | # Depends on UserPool for Id 317 | # Depends on UserPoolClient for Id 318 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 319 | Type: 'Custom::LambdaCallout' 320 | Properties: 321 | ServiceToken: !GetAtt UserPoolClientLambda.Arn 322 | clientId: !Ref UserPoolClient 323 | userpoolId: !Ref UserPool 324 | DependsOn: UserPoolClientLogPolicy 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | # BEGIN IDENTITY POOL RESOURCES 333 | 334 | 335 | IdentityPool: 336 | # Always created 337 | Type: AWS::Cognito::IdentityPool 338 | Properties: 339 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'gqls3testeb3f615e_identitypool_eb3f615e', !Join ['',['gqls3testeb3f615e_identitypool_eb3f615e', '__', !Ref env]]] 340 | 341 | CognitoIdentityProviders: 342 | - ClientId: !Ref UserPoolClient 343 | ProviderName: !Sub 344 | - cognito-idp.${region}.amazonaws.com/${client} 345 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 346 | - ClientId: !Ref UserPoolClientWeb 347 | ProviderName: !Sub 348 | - cognito-idp.${region}.amazonaws.com/${client} 349 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 350 | 351 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities 352 | 353 | 354 | DependsOn: UserPoolClientInputs 355 | 356 | 357 | IdentityPoolRoleMap: 358 | # Created to map Auth and Unauth roles to the identity pool 359 | # Depends on Identity Pool for ID ref 360 | Type: AWS::Cognito::IdentityPoolRoleAttachment 361 | Properties: 362 | IdentityPoolId: !Ref IdentityPool 363 | Roles: 364 | unauthenticated: !Ref unauthRoleArn 365 | authenticated: !Ref authRoleArn 366 | DependsOn: IdentityPool 367 | 368 | 369 | Outputs : 370 | 371 | IdentityPoolId: 372 | Value: !Ref 'IdentityPool' 373 | Description: Id for the identity pool 374 | IdentityPoolName: 375 | Value: !GetAtt IdentityPool.Name 376 | 377 | 378 | 379 | 380 | UserPoolId: 381 | Value: !Ref 'UserPool' 382 | Description: Id for the user pool 383 | UserPoolName: 384 | Value: !Ref userPoolName 385 | AppClientIDWeb: 386 | Value: !Ref 'UserPoolClientWeb' 387 | Description: The user pool app client id for web 388 | AppClientID: 389 | Value: !Ref 'UserPoolClient' 390 | Description: The user pool app client id 391 | AppClientSecret: 392 | Value: !GetAtt UserPoolClientInputs.appSecret 393 | 394 | 395 | 396 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /amplify/backend/auth/gqls3testeb3f615e/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "gqls3testeb3f615e_identitypool_eb3f615e", 3 | "allowUnauthenticatedIdentities": true, 4 | "lambdaLogPolicy": "gqls3t_eb3f615e_lambda_log_policy", 5 | "openIdLambdaRoleName": "gqls3t_eb3f615e_openid_lambda_role", 6 | "openIdRolePolicy": "gqls3t_eb3f615e_openid_pass_role_policy", 7 | "openIdLambdaIAMPolicy": "gqls3t_eb3f615e_openid_lambda_iam_policy", 8 | "openIdLogPolicy": "gqls3t_eb3f615e_openid_lambda_log_policy", 9 | "userPoolName": "gqls3testeb3f615e_userpool_eb3f615e", 10 | "autoVerifiedAttributes": [ 11 | "email" 12 | ], 13 | "mfaConfiguration": "OFF", 14 | "mfaTypes": [ 15 | "SMS Text Message" 16 | ], 17 | "roleName": "gqls3teb3f615e_sns-role", 18 | "roleExternalId": "gqls3teb3f615e_role_external_id", 19 | "policyName": "gqls3teb3f615e-sns-policy", 20 | "smsAuthenticationMessage": "Your authentication code is {####}", 21 | "smsVerificationMessage": "Your verification code is {####}", 22 | "emailVerificationSubject": "Your verification code", 23 | "emailVerificationMessage": "Your verification code is {####}", 24 | "defaultPasswordPolicy": false, 25 | "passwordPolicyMinLength": 8, 26 | "passwordPolicyCharacters": [], 27 | "requiredAttributes": [ 28 | "email" 29 | ], 30 | "userpoolClientName": "gqls3teb3f615e_app_client", 31 | "userpoolClientGenerateSecret": true, 32 | "userpoolClientRefreshTokenValidity": 30, 33 | "userpoolClientWriteAttributes": [ 34 | "email" 35 | ], 36 | "userpoolClientReadAttributes": [ 37 | "email" 38 | ], 39 | "mfaLambdaRole": "gqls3teb3f615e_totp_lambda_role", 40 | "mfaLambdaLogPolicy": "gqls3teb3f615e_totp_lambda_log_policy", 41 | "mfaPassRolePolicy": "gqls3teb3f615e_totp_pass_role_policy", 42 | "mfaLambdaIAMPolicy": "gqls3teb3f615e_totp_lambda_iam_policy", 43 | "userpoolClientLambdaRole": "gqls3teb3f615e_userpoolclient_lambda_role", 44 | "userpoolClientLogPolicy": "gqls3teb3f615e_userpoolclient_lambda_log_policy", 45 | "userpoolClientLambdaPolicy": "gqls3teb3f615e_userpoolclient_lambda_iam_policy", 46 | "userpoolClientSetAttributes": false, 47 | "resourceName": "gqls3testeb3f615e", 48 | "authSelections": "identityPoolAndUserPool", 49 | "authRoleName": { 50 | "Ref": "AuthRoleName" 51 | }, 52 | "unauthRoleName": { 53 | "Ref": "UnauthRoleName" 54 | }, 55 | "authRoleArn": { 56 | "Fn::GetAtt": [ 57 | "AuthRole", 58 | "Arn" 59 | ] 60 | }, 61 | "unauthRoleArn": { 62 | "Fn::GetAtt": [ 63 | "UnauthRole", 64 | "Arn" 65 | ] 66 | }, 67 | "useDefault": "default" 68 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "gqls3testeb3f615e": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation" 6 | } 7 | }, 8 | "storage": { 9 | "gqls3test": { 10 | "service": "S3", 11 | "providerPlugin": "awscloudformation" 12 | } 13 | }, 14 | "api": { 15 | "gqls3test": { 16 | "service": "AppSync", 17 | "providerPlugin": "awscloudformation", 18 | "output": { 19 | "securityType": "AMAZON_COGNITO_USER_POOLS" 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /amplify/backend/storage/gqls3test/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucketName": "gqls3test9cf440e7c85b495793505c36abdf365a", 3 | "authPolicyName": "s3_amplify_5c16ce3b", 4 | "unauthPolicyName": "s3_amplify_5c16ce3b", 5 | "authRoleName": { 6 | "Ref": "AuthRoleName" 7 | }, 8 | "unauthRoleName": { 9 | "Ref": "UnauthRoleName" 10 | }, 11 | "selectedGuestPermissions": [ 12 | "s3:GetObject", 13 | "s3:ListBucket" 14 | ], 15 | "selectedAuthenticatedPermissions": [ 16 | "s3:PutObject", 17 | "s3:GetObject", 18 | "s3:ListBucket", 19 | "s3:DeleteObject" 20 | ], 21 | "s3PermissionsAuthenticatedPublic": "s3:PutObject,s3:GetObject,s3:DeleteObject", 22 | "s3PublicPolicy": "Public_policy_7ec660c0", 23 | "s3PermissionsAuthenticatedUploads": "s3:PutObject", 24 | "s3UploadsPolicy": "Uploads_policy_7ec660c0", 25 | "s3PermissionsAuthenticatedProtected": "s3:PutObject,s3:GetObject,s3:DeleteObject", 26 | "s3ProtectedPolicy": "Protected_policy_cca4785b", 27 | "s3PermissionsAuthenticatedPrivate": "s3:PutObject,s3:GetObject,s3:DeleteObject", 28 | "s3PrivatePolicy": "Private_policy_cca4785b", 29 | "AuthenticatedAllowList": "ALLOW", 30 | "s3ReadPolicy": "read_policy_7ec660c0", 31 | "s3PermissionsGuestPublic": "s3:GetObject", 32 | "s3PermissionsGuestUploads": "DISALLOW", 33 | "GuestAllowList": "ALLOW" 34 | } -------------------------------------------------------------------------------- /amplify/backend/storage/gqls3test/s3-cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "S3 resource stack creation using Amplify CLI", 4 | "Parameters": { 5 | "bucketName": { 6 | "Type": "String" 7 | }, 8 | "authPolicyName": { 9 | "Type": "String" 10 | }, 11 | "unauthPolicyName": { 12 | "Type": "String" 13 | }, 14 | "authRoleName": { 15 | "Type": "String" 16 | }, 17 | "unauthRoleName": { 18 | "Type": "String" 19 | }, 20 | "s3PublicPolicy": { 21 | "Type": "String" 22 | }, 23 | "s3PrivatePolicy": { 24 | "Type": "String" 25 | }, 26 | "s3ProtectedPolicy": { 27 | "Type": "String" 28 | }, 29 | "s3UploadsPolicy": { 30 | "Type": "String" 31 | }, 32 | "s3ReadPolicy": { 33 | "Type": "String" 34 | }, 35 | "s3PermissionsAuthenticatedPublic": { 36 | "Type": "String" 37 | }, 38 | "s3PermissionsAuthenticatedProtected": { 39 | "Type": "String" 40 | }, 41 | "s3PermissionsAuthenticatedPrivate": { 42 | "Type": "String" 43 | }, 44 | "s3PermissionsAuthenticatedUploads": { 45 | "Type": "String" 46 | }, 47 | "s3PermissionsGuestPublic": { 48 | "Type": "String", 49 | "Default" : "DISALLOW" 50 | }, 51 | "s3PermissionsGuestUploads": { 52 | "Type": "String", 53 | "Default" : "DISALLOW" }, 54 | "AuthenticatedAllowList": { 55 | "Type": "String" 56 | }, 57 | "GuestAllowList": { 58 | "Type": "String", 59 | "Default" : "DISALLOW" 60 | }, 61 | "selectedGuestPermissions": { 62 | "Type": "CommaDelimitedList" 63 | }, 64 | "selectedAuthenticatedPermissions": { 65 | "Type": "CommaDelimitedList" 66 | }, 67 | "env": { 68 | "Type": "String" 69 | } 70 | }, 71 | "Conditions": { 72 | "ShouldNotCreateEnvResources": { 73 | "Fn::Equals": [ 74 | { 75 | "Ref": "env" 76 | }, 77 | "NONE" 78 | ] 79 | }, 80 | "CreateAuthPublic": { 81 | "Fn::Not" : [{ 82 | "Fn::Equals" : [ 83 | {"Ref" : "s3PermissionsAuthenticatedPublic"}, 84 | "DISALLOW" 85 | ] 86 | }] 87 | }, 88 | "CreateAuthProtected": { 89 | "Fn::Not" : [{ 90 | "Fn::Equals" : [ 91 | {"Ref" : "s3PermissionsAuthenticatedProtected"}, 92 | "DISALLOW" 93 | ] 94 | }] 95 | }, 96 | "CreateAuthPrivate": { 97 | "Fn::Not" : [{ 98 | "Fn::Equals" : [ 99 | {"Ref" : "s3PermissionsAuthenticatedPrivate"}, 100 | "DISALLOW" 101 | ] 102 | }] 103 | }, 104 | "CreateAuthUploads": { 105 | "Fn::Not" : [{ 106 | "Fn::Equals" : [ 107 | {"Ref" : "s3PermissionsAuthenticatedUploads"}, 108 | "DISALLOW" 109 | ] 110 | }] 111 | }, 112 | "CreateGuestPublic": { 113 | "Fn::Not" : [{ 114 | "Fn::Equals" : [ 115 | {"Ref" : "s3PermissionsGuestPublic"}, 116 | "DISALLOW" 117 | ] 118 | }] 119 | }, 120 | "CreateGuestUploads": { 121 | "Fn::Not" : [{ 122 | "Fn::Equals" : [ 123 | {"Ref" : "s3PermissionsGuestUploads"}, 124 | "DISALLOW" 125 | ] 126 | }] 127 | }, 128 | "AuthReadAndList": { 129 | "Fn::Not" : [{ 130 | "Fn::Equals" : [ 131 | {"Ref" : "AuthenticatedAllowList"}, 132 | "DISALLOW" 133 | ] 134 | }] 135 | }, 136 | "GuestReadAndList": { 137 | "Fn::Not" : [{ 138 | "Fn::Equals" : [ 139 | {"Ref" : "GuestAllowList"}, 140 | "DISALLOW" 141 | ] 142 | }] 143 | } 144 | }, 145 | "Resources": { 146 | "S3Bucket": { 147 | "Type": "AWS::S3::Bucket", 148 | "DeletionPolicy" : "Retain", 149 | "Properties": { 150 | "BucketName": { 151 | "Fn::If": [ 152 | "ShouldNotCreateEnvResources", 153 | { 154 | "Ref": "bucketName" 155 | }, 156 | { 157 | "Fn::Join": [ 158 | "", 159 | [ 160 | { 161 | "Ref": "bucketName" 162 | }, 163 | "-", 164 | { 165 | "Ref": "env" 166 | } 167 | ] 168 | ] 169 | } 170 | ] 171 | }, 172 | "CorsConfiguration": { 173 | "CorsRules": [ 174 | { 175 | "AllowedHeaders": [ 176 | "*" 177 | ], 178 | "AllowedMethods": [ 179 | "GET", 180 | "HEAD", 181 | "PUT", 182 | "POST", 183 | "DELETE" 184 | ], 185 | "AllowedOrigins": [ 186 | "*" 187 | ], 188 | "ExposedHeaders": [ 189 | "x-amz-server-side-encryption", 190 | "x-amz-request-id", 191 | "x-amz-id-2", 192 | "ETag" 193 | ], 194 | "Id": "S3CORSRuleId1", 195 | "MaxAge": "3000" 196 | } 197 | ] 198 | } 199 | } 200 | }, 201 | "S3AuthPublicPolicy": { 202 | "DependsOn": [ 203 | "S3Bucket" 204 | ], 205 | "Condition": "CreateAuthPublic", 206 | "Type": "AWS::IAM::Policy", 207 | "Properties": { 208 | "PolicyName": { 209 | "Ref": "s3PublicPolicy" 210 | }, 211 | "Roles": [ 212 | { 213 | "Ref": "authRoleName" 214 | } 215 | ], 216 | "PolicyDocument": { 217 | "Version": "2012-10-17", 218 | "Statement": [ 219 | { 220 | "Effect": "Allow", 221 | "Action": { 222 | "Fn::Split" : [ "," , { 223 | "Ref": "s3PermissionsAuthenticatedPublic" 224 | } ] 225 | }, 226 | "Resource": [ 227 | { 228 | "Fn::Join": [ 229 | "", 230 | [ 231 | "arn:aws:s3:::", 232 | { 233 | "Ref": "S3Bucket" 234 | }, 235 | "/public/*" 236 | ] 237 | ] 238 | } 239 | ] 240 | } 241 | ] 242 | } 243 | } 244 | }, 245 | "S3AuthProtectedPolicy": { 246 | "DependsOn": [ 247 | "S3Bucket" 248 | ], 249 | "Condition": "CreateAuthProtected", 250 | "Type": "AWS::IAM::Policy", 251 | "Properties": { 252 | "PolicyName": { 253 | "Ref": "s3ProtectedPolicy" 254 | }, 255 | "Roles": [ 256 | { 257 | "Ref": "authRoleName" 258 | } 259 | ], 260 | "PolicyDocument": { 261 | "Version": "2012-10-17", 262 | "Statement": [ 263 | { 264 | "Effect": "Allow", 265 | "Action": { 266 | "Fn::Split" : [ "," , { 267 | "Ref": "s3PermissionsAuthenticatedProtected" 268 | } ] 269 | }, 270 | "Resource": [ 271 | { 272 | "Fn::Join": [ 273 | "", 274 | [ 275 | "arn:aws:s3:::", 276 | { 277 | "Ref": "S3Bucket" 278 | }, 279 | "/protected/${cognito-identity.amazonaws.com:sub}/*" 280 | ] 281 | ] 282 | } 283 | ] 284 | } 285 | ] 286 | } 287 | } 288 | }, 289 | "S3AuthPrivatePolicy": { 290 | "DependsOn": [ 291 | "S3Bucket" 292 | ], 293 | "Condition": "CreateAuthPrivate", 294 | "Type": "AWS::IAM::Policy", 295 | "Properties": { 296 | "PolicyName": { 297 | "Ref": "s3PrivatePolicy" 298 | }, 299 | "Roles": [ 300 | { 301 | "Ref": "authRoleName" 302 | } 303 | ], 304 | "PolicyDocument": { 305 | "Version": "2012-10-17", 306 | "Statement": [ 307 | { 308 | "Effect": "Allow", 309 | "Action": { 310 | "Fn::Split" : [ "," , { 311 | "Ref": "s3PermissionsAuthenticatedPrivate" 312 | } ] 313 | }, 314 | "Resource": [ 315 | { 316 | "Fn::Join": [ 317 | "", 318 | [ 319 | "arn:aws:s3:::", 320 | { 321 | "Ref": "S3Bucket" 322 | }, 323 | "/private/${cognito-identity.amazonaws.com:sub}/*" 324 | ] 325 | ] 326 | } 327 | ] 328 | } 329 | ] 330 | } 331 | } 332 | }, 333 | "S3AuthUploadPolicy": { 334 | "DependsOn": [ 335 | "S3Bucket" 336 | ], 337 | "Condition": "CreateAuthUploads", 338 | "Type": "AWS::IAM::Policy", 339 | "Properties": { 340 | "PolicyName": { 341 | "Ref": "s3UploadsPolicy" 342 | }, 343 | "Roles": [ 344 | { 345 | "Ref": "authRoleName" 346 | } 347 | ], 348 | "PolicyDocument": { 349 | "Version": "2012-10-17", 350 | "Statement": [ 351 | { 352 | "Effect": "Allow", 353 | "Action": { 354 | "Fn::Split" : [ "," , { 355 | "Ref": "s3PermissionsAuthenticatedUploads" 356 | } ] 357 | }, 358 | "Resource": [ 359 | { 360 | "Fn::Join": [ 361 | "", 362 | [ 363 | "arn:aws:s3:::", 364 | { 365 | "Ref": "S3Bucket" 366 | }, 367 | "/uploads/*" 368 | ] 369 | ] 370 | } 371 | ] 372 | } 373 | ] 374 | } 375 | } 376 | }, 377 | "S3AuthReadPolicy": { 378 | "DependsOn": [ 379 | "S3Bucket" 380 | ], 381 | "Condition": "AuthReadAndList", 382 | "Type": "AWS::IAM::Policy", 383 | "Properties": { 384 | "PolicyName": { 385 | "Ref": "s3ReadPolicy" 386 | }, 387 | "Roles": [ 388 | { 389 | "Ref": "authRoleName" 390 | } 391 | ], 392 | "PolicyDocument": { 393 | "Version": "2012-10-17", 394 | "Statement": [ 395 | { 396 | "Effect": "Allow", 397 | "Action": [ 398 | "s3:GetObject" 399 | ], 400 | "Resource": [ 401 | { 402 | "Fn::Join": [ 403 | "", 404 | [ 405 | "arn:aws:s3:::", 406 | { 407 | "Ref": "S3Bucket" 408 | }, 409 | "/protected/*" 410 | ] 411 | ] 412 | } 413 | ] 414 | }, 415 | { 416 | "Effect": "Allow", 417 | "Action": [ 418 | "s3:ListBucket" 419 | ], 420 | "Resource": [ 421 | { 422 | "Fn::Join": [ 423 | "", 424 | [ 425 | "arn:aws:s3:::", 426 | { 427 | "Ref": "S3Bucket" 428 | } 429 | ] 430 | ] 431 | } 432 | ], 433 | "Condition": { 434 | "StringLike": { 435 | "s3:prefix": [ 436 | "public/", 437 | "public/*", 438 | "protected/", 439 | "protected/*", 440 | "private/${cognito-identity.amazonaws.com:sub}/", 441 | "private/${cognito-identity.amazonaws.com:sub}/*" 442 | ] 443 | } 444 | } 445 | } 446 | ] 447 | } 448 | } 449 | }, 450 | "S3GuestPublicPolicy": { 451 | "DependsOn": [ 452 | "S3Bucket" 453 | ], 454 | "Condition": "CreateGuestPublic", 455 | "Type": "AWS::IAM::Policy", 456 | "Properties": { 457 | "PolicyName": { 458 | "Ref": "s3PublicPolicy" 459 | }, 460 | "Roles": [ 461 | { 462 | "Ref": "unauthRoleName" 463 | } 464 | ], 465 | "PolicyDocument": { 466 | "Version": "2012-10-17", 467 | "Statement": [ 468 | { 469 | "Effect": "Allow", 470 | "Action": { 471 | "Fn::Split" : [ "," , { 472 | "Ref": "s3PermissionsGuestPublic" 473 | } ] 474 | }, 475 | "Resource": [ 476 | { 477 | "Fn::Join": [ 478 | "", 479 | [ 480 | "arn:aws:s3:::", 481 | { 482 | "Ref": "S3Bucket" 483 | }, 484 | "/public/*" 485 | ] 486 | ] 487 | } 488 | ] 489 | } 490 | ] 491 | } 492 | } 493 | }, 494 | "S3GuestUploadPolicy": { 495 | "DependsOn": [ 496 | "S3Bucket" 497 | ], 498 | "Condition": "CreateGuestUploads", 499 | "Type": "AWS::IAM::Policy", 500 | "Properties": { 501 | "PolicyName": { 502 | "Ref": "s3UploadsPolicy" 503 | }, 504 | "Roles": [ 505 | { 506 | "Ref": "unauthRoleName" 507 | } 508 | ], 509 | "PolicyDocument": { 510 | "Version": "2012-10-17", 511 | "Statement": [ 512 | { 513 | "Effect": "Allow", 514 | "Action": { 515 | "Fn::Split" : [ "," , { 516 | "Ref": "s3PermissionsGuestUploads" 517 | } ] 518 | }, 519 | "Resource": [ 520 | { 521 | "Fn::Join": [ 522 | "", 523 | [ 524 | "arn:aws:s3:::", 525 | { 526 | "Ref": "S3Bucket" 527 | }, 528 | "/uploads/*" 529 | ] 530 | ] 531 | } 532 | ] 533 | } 534 | ] 535 | } 536 | } 537 | }, 538 | "S3GuestReadPolicy": { 539 | "DependsOn": [ 540 | "S3Bucket" 541 | ], 542 | "Condition": "GuestReadAndList", 543 | "Type": "AWS::IAM::Policy", 544 | "Properties": { 545 | "PolicyName": { 546 | "Ref": "s3ReadPolicy" 547 | }, 548 | "Roles": [ 549 | { 550 | "Ref": "unauthRoleName" 551 | } 552 | ], 553 | "PolicyDocument": { 554 | "Version": "2012-10-17", 555 | "Statement": [ 556 | { 557 | "Effect": "Allow", 558 | "Action": [ 559 | "s3:GetObject" 560 | ], 561 | "Resource": [ 562 | { 563 | "Fn::Join": [ 564 | "", 565 | [ 566 | "arn:aws:s3:::", 567 | { 568 | "Ref": "S3Bucket" 569 | }, 570 | "/protected/*" 571 | ] 572 | ] 573 | } 574 | ] 575 | }, 576 | { 577 | "Effect": "Allow", 578 | "Action": [ 579 | "s3:ListBucket" 580 | ], 581 | "Resource": [ 582 | { 583 | "Fn::Join": [ 584 | "", 585 | [ 586 | "arn:aws:s3:::", 587 | { 588 | "Ref": "S3Bucket" 589 | } 590 | ] 591 | ] 592 | } 593 | ], 594 | "Condition": { 595 | "StringLike": { 596 | "s3:prefix": [ 597 | "public/", 598 | "public/*", 599 | "protected/", 600 | "protected/*" 601 | ] 602 | } 603 | } 604 | } 605 | ] 606 | } 607 | } 608 | } 609 | }, 610 | "Outputs": { 611 | "BucketName": { 612 | "Value": { 613 | "Ref": "S3Bucket" 614 | }, 615 | "Description": "Bucket name for the S3 bucket" 616 | }, 617 | "Region": { 618 | "Value": { 619 | "Ref": "AWS::Region" 620 | } 621 | } 622 | } 623 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gqls3test", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.29", 7 | "aws-amplify-react": "^2.3.9", 8 | "react": "^16.8.6", 9 | "react-dom": "^16.8.6", 10 | "react-scripts": "3.0.1", 11 | "uuid": "^3.3.2" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-appsync-graphql-images-s3/76cb213bb2685e6b626970923638e89ea4cc6076/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useReducer, useEffect } from 'react' 2 | import './App.css' 3 | import { withAuthenticator } from 'aws-amplify-react' 4 | import { Storage, Auth, API, graphqlOperation } from 'aws-amplify' 5 | import uuid from 'uuid/v4' 6 | import { createUser } from './graphql/mutations' 7 | import { listUsers } from './graphql/queries' 8 | import { onCreateUser } from './graphql/subscriptions' 9 | import ampConfig from './aws-exports' 10 | 11 | const { 12 | aws_user_files_s3_bucket_region: region, 13 | aws_user_files_s3_bucket: bucket 14 | } = ampConfig 15 | 16 | const initialState = { 17 | users: [] 18 | } 19 | 20 | function reducer(state, action) { 21 | switch(action.type) { 22 | case 'SET_USERS': 23 | return { ...state, users: action.users } 24 | case 'ADD_USER': 25 | return { ...state, users: [action.user, ...state.users] } 26 | default: 27 | return state 28 | } 29 | } 30 | 31 | function App() { 32 | const [file, updateFile] = useState({}) 33 | const [state, dispatch] = useReducer(reducer, initialState) 34 | 35 | function handleChange(event) { 36 | const { target: { value, files } } = event 37 | const [image] = files || [] 38 | updateFile(image || value) 39 | } 40 | 41 | async function fetchUsers() { 42 | try { 43 | // fetch all items from DB 44 | let users = await API.graphql(graphqlOperation(listUsers)) 45 | users = users.data.listUsers.items 46 | // create Amazon S3 api calls for items in list 47 | const userRequests = users.map(u => Storage.get(u.avatar.key)) 48 | // get signed Image URLs from S3 for each item in array by making the API call 49 | const userData = await(Promise.all(userRequests)) 50 | // add new signed url to each item in array 51 | users.forEach((u, i) => { 52 | u.avatarUrl = userData[i] 53 | }) 54 | dispatch({ type: 'SET_USERS', users }) 55 | } catch(err) { 56 | console.log('error fetching users') 57 | } 58 | } 59 | 60 | async function handleSubmit(event) { 61 | event.preventDefault() 62 | const { identityId } = await Auth.currentCredentials() 63 | const { username } = await Auth.currentUserInfo() 64 | 65 | if (file) { 66 | const { name: fileName, type: mimeType } = file 67 | const key = `${identityId}/${uuid()}${fileName}` 68 | const fileForUpload = { 69 | bucket, 70 | key, 71 | region, 72 | } 73 | const inputData = { username, avatar: fileForUpload } 74 | 75 | try { 76 | await Storage.put(key, file, { 77 | contentType: mimeType 78 | }) 79 | await API.graphql(graphqlOperation(createUser, { input: inputData })) 80 | console.log('successfully stored user data!') 81 | } catch (err) { 82 | console.log('error: ', err) 83 | } 84 | } 85 | } 86 | useEffect(() => { 87 | fetchUsers() 88 | const subscription = API.graphql(graphqlOperation(onCreateUser)) 89 | .subscribe({ 90 | next: async userData => { 91 | const { onCreateUser } = userData.value.data 92 | const avatarUrl = await Storage.get(onCreateUser.avatar.key) 93 | onCreateUser['avatarUrl'] = avatarUrl 94 | dispatch({ type: 'ADD_USER', user: onCreateUser }) 95 | } 96 | }) 97 | return () => subscription.unsubscribe() 98 | }, []) 99 | 100 | return ( 101 |
102 | 108 | 111 | { 112 | state.users.map((u, i) => { 113 | return ( 114 | 119 | ) 120 | }) 121 | } 122 |
123 | ) 124 | } 125 | 126 | const styles = { 127 | container: { 128 | width: 100, 129 | margin: '0 auto' 130 | }, 131 | button: { 132 | width: 200, 133 | backgroundColor: '#ddd', 134 | cursor: 'pointer', 135 | height: 30, 136 | margin: '0px 0px 8px' 137 | } 138 | } 139 | 140 | export default withAuthenticator(App, { includeGreetings: true }) -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const createUser = `mutation CreateUser($input: CreateUserInput!) { 5 | createUser(input: $input) { 6 | id 7 | username 8 | visibility 9 | avatar { 10 | bucket 11 | region 12 | key 13 | } 14 | } 15 | } 16 | `; 17 | export const updateUser = `mutation UpdateUser($input: UpdateUserInput!) { 18 | updateUser(input: $input) { 19 | id 20 | username 21 | visibility 22 | avatar { 23 | bucket 24 | region 25 | key 26 | } 27 | } 28 | } 29 | `; 30 | export const deleteUser = `mutation DeleteUser($input: DeleteUserInput!) { 31 | deleteUser(input: $input) { 32 | id 33 | username 34 | visibility 35 | avatar { 36 | bucket 37 | region 38 | key 39 | } 40 | } 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const getUser = `query GetUser($id: ID!) { 5 | getUser(id: $id) { 6 | id 7 | username 8 | visibility 9 | avatar { 10 | bucket 11 | region 12 | key 13 | } 14 | } 15 | } 16 | `; 17 | export const listUsers = `query ListUsers( 18 | $filter: ModelUserFilterInput 19 | $limit: Int 20 | $nextToken: String 21 | ) { 22 | listUsers(filter: $filter, limit: $limit, nextToken: $nextToken) { 23 | items { 24 | id 25 | username 26 | visibility 27 | avatar { 28 | bucket 29 | region 30 | key 31 | } 32 | } 33 | nextToken 34 | } 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/graphql/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "__schema" : { 4 | "queryType" : { 5 | "name" : "Query" 6 | }, 7 | "mutationType" : { 8 | "name" : "Mutation" 9 | }, 10 | "subscriptionType" : { 11 | "name" : "Subscription" 12 | }, 13 | "types" : [ { 14 | "kind" : "OBJECT", 15 | "name" : "Query", 16 | "description" : null, 17 | "fields" : [ { 18 | "name" : "getUser", 19 | "description" : null, 20 | "args" : [ { 21 | "name" : "id", 22 | "description" : null, 23 | "type" : { 24 | "kind" : "NON_NULL", 25 | "name" : null, 26 | "ofType" : { 27 | "kind" : "SCALAR", 28 | "name" : "ID", 29 | "ofType" : null 30 | } 31 | }, 32 | "defaultValue" : null 33 | } ], 34 | "type" : { 35 | "kind" : "OBJECT", 36 | "name" : "User", 37 | "ofType" : null 38 | }, 39 | "isDeprecated" : false, 40 | "deprecationReason" : null 41 | }, { 42 | "name" : "listUsers", 43 | "description" : null, 44 | "args" : [ { 45 | "name" : "filter", 46 | "description" : null, 47 | "type" : { 48 | "kind" : "INPUT_OBJECT", 49 | "name" : "ModelUserFilterInput", 50 | "ofType" : null 51 | }, 52 | "defaultValue" : null 53 | }, { 54 | "name" : "limit", 55 | "description" : null, 56 | "type" : { 57 | "kind" : "SCALAR", 58 | "name" : "Int", 59 | "ofType" : null 60 | }, 61 | "defaultValue" : null 62 | }, { 63 | "name" : "nextToken", 64 | "description" : null, 65 | "type" : { 66 | "kind" : "SCALAR", 67 | "name" : "String", 68 | "ofType" : null 69 | }, 70 | "defaultValue" : null 71 | } ], 72 | "type" : { 73 | "kind" : "OBJECT", 74 | "name" : "ModelUserConnection", 75 | "ofType" : null 76 | }, 77 | "isDeprecated" : false, 78 | "deprecationReason" : null 79 | } ], 80 | "inputFields" : null, 81 | "interfaces" : [ ], 82 | "enumValues" : null, 83 | "possibleTypes" : null 84 | }, { 85 | "kind" : "OBJECT", 86 | "name" : "User", 87 | "description" : null, 88 | "fields" : [ { 89 | "name" : "id", 90 | "description" : null, 91 | "args" : [ ], 92 | "type" : { 93 | "kind" : "NON_NULL", 94 | "name" : null, 95 | "ofType" : { 96 | "kind" : "SCALAR", 97 | "name" : "ID", 98 | "ofType" : null 99 | } 100 | }, 101 | "isDeprecated" : false, 102 | "deprecationReason" : null 103 | }, { 104 | "name" : "username", 105 | "description" : null, 106 | "args" : [ ], 107 | "type" : { 108 | "kind" : "SCALAR", 109 | "name" : "String", 110 | "ofType" : null 111 | }, 112 | "isDeprecated" : false, 113 | "deprecationReason" : null 114 | }, { 115 | "name" : "visibility", 116 | "description" : null, 117 | "args" : [ ], 118 | "type" : { 119 | "kind" : "ENUM", 120 | "name" : "Visibility", 121 | "ofType" : null 122 | }, 123 | "isDeprecated" : false, 124 | "deprecationReason" : null 125 | }, { 126 | "name" : "avatar", 127 | "description" : null, 128 | "args" : [ ], 129 | "type" : { 130 | "kind" : "OBJECT", 131 | "name" : "S3Object", 132 | "ofType" : null 133 | }, 134 | "isDeprecated" : false, 135 | "deprecationReason" : null 136 | } ], 137 | "inputFields" : null, 138 | "interfaces" : [ ], 139 | "enumValues" : null, 140 | "possibleTypes" : null 141 | }, { 142 | "kind" : "SCALAR", 143 | "name" : "ID", 144 | "description" : "Built-in ID", 145 | "fields" : null, 146 | "inputFields" : null, 147 | "interfaces" : null, 148 | "enumValues" : null, 149 | "possibleTypes" : null 150 | }, { 151 | "kind" : "SCALAR", 152 | "name" : "String", 153 | "description" : "Built-in String", 154 | "fields" : null, 155 | "inputFields" : null, 156 | "interfaces" : null, 157 | "enumValues" : null, 158 | "possibleTypes" : null 159 | }, { 160 | "kind" : "ENUM", 161 | "name" : "Visibility", 162 | "description" : null, 163 | "fields" : null, 164 | "inputFields" : null, 165 | "interfaces" : null, 166 | "enumValues" : [ { 167 | "name" : "public", 168 | "description" : null, 169 | "isDeprecated" : false, 170 | "deprecationReason" : null 171 | }, { 172 | "name" : "private", 173 | "description" : null, 174 | "isDeprecated" : false, 175 | "deprecationReason" : null 176 | } ], 177 | "possibleTypes" : null 178 | }, { 179 | "kind" : "OBJECT", 180 | "name" : "S3Object", 181 | "description" : null, 182 | "fields" : [ { 183 | "name" : "bucket", 184 | "description" : null, 185 | "args" : [ ], 186 | "type" : { 187 | "kind" : "NON_NULL", 188 | "name" : null, 189 | "ofType" : { 190 | "kind" : "SCALAR", 191 | "name" : "String", 192 | "ofType" : null 193 | } 194 | }, 195 | "isDeprecated" : false, 196 | "deprecationReason" : null 197 | }, { 198 | "name" : "region", 199 | "description" : null, 200 | "args" : [ ], 201 | "type" : { 202 | "kind" : "NON_NULL", 203 | "name" : null, 204 | "ofType" : { 205 | "kind" : "SCALAR", 206 | "name" : "String", 207 | "ofType" : null 208 | } 209 | }, 210 | "isDeprecated" : false, 211 | "deprecationReason" : null 212 | }, { 213 | "name" : "key", 214 | "description" : null, 215 | "args" : [ ], 216 | "type" : { 217 | "kind" : "NON_NULL", 218 | "name" : null, 219 | "ofType" : { 220 | "kind" : "SCALAR", 221 | "name" : "String", 222 | "ofType" : null 223 | } 224 | }, 225 | "isDeprecated" : false, 226 | "deprecationReason" : null 227 | } ], 228 | "inputFields" : null, 229 | "interfaces" : [ ], 230 | "enumValues" : null, 231 | "possibleTypes" : null 232 | }, { 233 | "kind" : "OBJECT", 234 | "name" : "ModelUserConnection", 235 | "description" : null, 236 | "fields" : [ { 237 | "name" : "items", 238 | "description" : null, 239 | "args" : [ ], 240 | "type" : { 241 | "kind" : "LIST", 242 | "name" : null, 243 | "ofType" : { 244 | "kind" : "OBJECT", 245 | "name" : "User", 246 | "ofType" : null 247 | } 248 | }, 249 | "isDeprecated" : false, 250 | "deprecationReason" : null 251 | }, { 252 | "name" : "nextToken", 253 | "description" : null, 254 | "args" : [ ], 255 | "type" : { 256 | "kind" : "SCALAR", 257 | "name" : "String", 258 | "ofType" : null 259 | }, 260 | "isDeprecated" : false, 261 | "deprecationReason" : null 262 | } ], 263 | "inputFields" : null, 264 | "interfaces" : [ ], 265 | "enumValues" : null, 266 | "possibleTypes" : null 267 | }, { 268 | "kind" : "INPUT_OBJECT", 269 | "name" : "ModelUserFilterInput", 270 | "description" : null, 271 | "fields" : null, 272 | "inputFields" : [ { 273 | "name" : "id", 274 | "description" : null, 275 | "type" : { 276 | "kind" : "INPUT_OBJECT", 277 | "name" : "ModelIDFilterInput", 278 | "ofType" : null 279 | }, 280 | "defaultValue" : null 281 | }, { 282 | "name" : "username", 283 | "description" : null, 284 | "type" : { 285 | "kind" : "INPUT_OBJECT", 286 | "name" : "ModelStringFilterInput", 287 | "ofType" : null 288 | }, 289 | "defaultValue" : null 290 | }, { 291 | "name" : "visibility", 292 | "description" : null, 293 | "type" : { 294 | "kind" : "INPUT_OBJECT", 295 | "name" : "ModelVisibilityFilterInput", 296 | "ofType" : null 297 | }, 298 | "defaultValue" : null 299 | }, { 300 | "name" : "and", 301 | "description" : null, 302 | "type" : { 303 | "kind" : "LIST", 304 | "name" : null, 305 | "ofType" : { 306 | "kind" : "INPUT_OBJECT", 307 | "name" : "ModelUserFilterInput", 308 | "ofType" : null 309 | } 310 | }, 311 | "defaultValue" : null 312 | }, { 313 | "name" : "or", 314 | "description" : null, 315 | "type" : { 316 | "kind" : "LIST", 317 | "name" : null, 318 | "ofType" : { 319 | "kind" : "INPUT_OBJECT", 320 | "name" : "ModelUserFilterInput", 321 | "ofType" : null 322 | } 323 | }, 324 | "defaultValue" : null 325 | }, { 326 | "name" : "not", 327 | "description" : null, 328 | "type" : { 329 | "kind" : "INPUT_OBJECT", 330 | "name" : "ModelUserFilterInput", 331 | "ofType" : null 332 | }, 333 | "defaultValue" : null 334 | } ], 335 | "interfaces" : null, 336 | "enumValues" : null, 337 | "possibleTypes" : null 338 | }, { 339 | "kind" : "INPUT_OBJECT", 340 | "name" : "ModelIDFilterInput", 341 | "description" : null, 342 | "fields" : null, 343 | "inputFields" : [ { 344 | "name" : "ne", 345 | "description" : null, 346 | "type" : { 347 | "kind" : "SCALAR", 348 | "name" : "ID", 349 | "ofType" : null 350 | }, 351 | "defaultValue" : null 352 | }, { 353 | "name" : "eq", 354 | "description" : null, 355 | "type" : { 356 | "kind" : "SCALAR", 357 | "name" : "ID", 358 | "ofType" : null 359 | }, 360 | "defaultValue" : null 361 | }, { 362 | "name" : "le", 363 | "description" : null, 364 | "type" : { 365 | "kind" : "SCALAR", 366 | "name" : "ID", 367 | "ofType" : null 368 | }, 369 | "defaultValue" : null 370 | }, { 371 | "name" : "lt", 372 | "description" : null, 373 | "type" : { 374 | "kind" : "SCALAR", 375 | "name" : "ID", 376 | "ofType" : null 377 | }, 378 | "defaultValue" : null 379 | }, { 380 | "name" : "ge", 381 | "description" : null, 382 | "type" : { 383 | "kind" : "SCALAR", 384 | "name" : "ID", 385 | "ofType" : null 386 | }, 387 | "defaultValue" : null 388 | }, { 389 | "name" : "gt", 390 | "description" : null, 391 | "type" : { 392 | "kind" : "SCALAR", 393 | "name" : "ID", 394 | "ofType" : null 395 | }, 396 | "defaultValue" : null 397 | }, { 398 | "name" : "contains", 399 | "description" : null, 400 | "type" : { 401 | "kind" : "SCALAR", 402 | "name" : "ID", 403 | "ofType" : null 404 | }, 405 | "defaultValue" : null 406 | }, { 407 | "name" : "notContains", 408 | "description" : null, 409 | "type" : { 410 | "kind" : "SCALAR", 411 | "name" : "ID", 412 | "ofType" : null 413 | }, 414 | "defaultValue" : null 415 | }, { 416 | "name" : "between", 417 | "description" : null, 418 | "type" : { 419 | "kind" : "LIST", 420 | "name" : null, 421 | "ofType" : { 422 | "kind" : "SCALAR", 423 | "name" : "ID", 424 | "ofType" : null 425 | } 426 | }, 427 | "defaultValue" : null 428 | }, { 429 | "name" : "beginsWith", 430 | "description" : null, 431 | "type" : { 432 | "kind" : "SCALAR", 433 | "name" : "ID", 434 | "ofType" : null 435 | }, 436 | "defaultValue" : null 437 | } ], 438 | "interfaces" : null, 439 | "enumValues" : null, 440 | "possibleTypes" : null 441 | }, { 442 | "kind" : "INPUT_OBJECT", 443 | "name" : "ModelStringFilterInput", 444 | "description" : null, 445 | "fields" : null, 446 | "inputFields" : [ { 447 | "name" : "ne", 448 | "description" : null, 449 | "type" : { 450 | "kind" : "SCALAR", 451 | "name" : "String", 452 | "ofType" : null 453 | }, 454 | "defaultValue" : null 455 | }, { 456 | "name" : "eq", 457 | "description" : null, 458 | "type" : { 459 | "kind" : "SCALAR", 460 | "name" : "String", 461 | "ofType" : null 462 | }, 463 | "defaultValue" : null 464 | }, { 465 | "name" : "le", 466 | "description" : null, 467 | "type" : { 468 | "kind" : "SCALAR", 469 | "name" : "String", 470 | "ofType" : null 471 | }, 472 | "defaultValue" : null 473 | }, { 474 | "name" : "lt", 475 | "description" : null, 476 | "type" : { 477 | "kind" : "SCALAR", 478 | "name" : "String", 479 | "ofType" : null 480 | }, 481 | "defaultValue" : null 482 | }, { 483 | "name" : "ge", 484 | "description" : null, 485 | "type" : { 486 | "kind" : "SCALAR", 487 | "name" : "String", 488 | "ofType" : null 489 | }, 490 | "defaultValue" : null 491 | }, { 492 | "name" : "gt", 493 | "description" : null, 494 | "type" : { 495 | "kind" : "SCALAR", 496 | "name" : "String", 497 | "ofType" : null 498 | }, 499 | "defaultValue" : null 500 | }, { 501 | "name" : "contains", 502 | "description" : null, 503 | "type" : { 504 | "kind" : "SCALAR", 505 | "name" : "String", 506 | "ofType" : null 507 | }, 508 | "defaultValue" : null 509 | }, { 510 | "name" : "notContains", 511 | "description" : null, 512 | "type" : { 513 | "kind" : "SCALAR", 514 | "name" : "String", 515 | "ofType" : null 516 | }, 517 | "defaultValue" : null 518 | }, { 519 | "name" : "between", 520 | "description" : null, 521 | "type" : { 522 | "kind" : "LIST", 523 | "name" : null, 524 | "ofType" : { 525 | "kind" : "SCALAR", 526 | "name" : "String", 527 | "ofType" : null 528 | } 529 | }, 530 | "defaultValue" : null 531 | }, { 532 | "name" : "beginsWith", 533 | "description" : null, 534 | "type" : { 535 | "kind" : "SCALAR", 536 | "name" : "String", 537 | "ofType" : null 538 | }, 539 | "defaultValue" : null 540 | } ], 541 | "interfaces" : null, 542 | "enumValues" : null, 543 | "possibleTypes" : null 544 | }, { 545 | "kind" : "INPUT_OBJECT", 546 | "name" : "ModelVisibilityFilterInput", 547 | "description" : null, 548 | "fields" : null, 549 | "inputFields" : [ { 550 | "name" : "eq", 551 | "description" : null, 552 | "type" : { 553 | "kind" : "ENUM", 554 | "name" : "Visibility", 555 | "ofType" : null 556 | }, 557 | "defaultValue" : null 558 | }, { 559 | "name" : "ne", 560 | "description" : null, 561 | "type" : { 562 | "kind" : "ENUM", 563 | "name" : "Visibility", 564 | "ofType" : null 565 | }, 566 | "defaultValue" : null 567 | } ], 568 | "interfaces" : null, 569 | "enumValues" : null, 570 | "possibleTypes" : null 571 | }, { 572 | "kind" : "SCALAR", 573 | "name" : "Int", 574 | "description" : "Built-in Int", 575 | "fields" : null, 576 | "inputFields" : null, 577 | "interfaces" : null, 578 | "enumValues" : null, 579 | "possibleTypes" : null 580 | }, { 581 | "kind" : "OBJECT", 582 | "name" : "Mutation", 583 | "description" : null, 584 | "fields" : [ { 585 | "name" : "createUser", 586 | "description" : null, 587 | "args" : [ { 588 | "name" : "input", 589 | "description" : null, 590 | "type" : { 591 | "kind" : "NON_NULL", 592 | "name" : null, 593 | "ofType" : { 594 | "kind" : "INPUT_OBJECT", 595 | "name" : "CreateUserInput", 596 | "ofType" : null 597 | } 598 | }, 599 | "defaultValue" : null 600 | } ], 601 | "type" : { 602 | "kind" : "OBJECT", 603 | "name" : "User", 604 | "ofType" : null 605 | }, 606 | "isDeprecated" : false, 607 | "deprecationReason" : null 608 | }, { 609 | "name" : "updateUser", 610 | "description" : null, 611 | "args" : [ { 612 | "name" : "input", 613 | "description" : null, 614 | "type" : { 615 | "kind" : "NON_NULL", 616 | "name" : null, 617 | "ofType" : { 618 | "kind" : "INPUT_OBJECT", 619 | "name" : "UpdateUserInput", 620 | "ofType" : null 621 | } 622 | }, 623 | "defaultValue" : null 624 | } ], 625 | "type" : { 626 | "kind" : "OBJECT", 627 | "name" : "User", 628 | "ofType" : null 629 | }, 630 | "isDeprecated" : false, 631 | "deprecationReason" : null 632 | }, { 633 | "name" : "deleteUser", 634 | "description" : null, 635 | "args" : [ { 636 | "name" : "input", 637 | "description" : null, 638 | "type" : { 639 | "kind" : "NON_NULL", 640 | "name" : null, 641 | "ofType" : { 642 | "kind" : "INPUT_OBJECT", 643 | "name" : "DeleteUserInput", 644 | "ofType" : null 645 | } 646 | }, 647 | "defaultValue" : null 648 | } ], 649 | "type" : { 650 | "kind" : "OBJECT", 651 | "name" : "User", 652 | "ofType" : null 653 | }, 654 | "isDeprecated" : false, 655 | "deprecationReason" : null 656 | } ], 657 | "inputFields" : null, 658 | "interfaces" : [ ], 659 | "enumValues" : null, 660 | "possibleTypes" : null 661 | }, { 662 | "kind" : "INPUT_OBJECT", 663 | "name" : "CreateUserInput", 664 | "description" : null, 665 | "fields" : null, 666 | "inputFields" : [ { 667 | "name" : "id", 668 | "description" : null, 669 | "type" : { 670 | "kind" : "SCALAR", 671 | "name" : "ID", 672 | "ofType" : null 673 | }, 674 | "defaultValue" : null 675 | }, { 676 | "name" : "username", 677 | "description" : null, 678 | "type" : { 679 | "kind" : "SCALAR", 680 | "name" : "String", 681 | "ofType" : null 682 | }, 683 | "defaultValue" : null 684 | }, { 685 | "name" : "visibility", 686 | "description" : null, 687 | "type" : { 688 | "kind" : "ENUM", 689 | "name" : "Visibility", 690 | "ofType" : null 691 | }, 692 | "defaultValue" : null 693 | }, { 694 | "name" : "avatar", 695 | "description" : null, 696 | "type" : { 697 | "kind" : "INPUT_OBJECT", 698 | "name" : "S3ObjectInput", 699 | "ofType" : null 700 | }, 701 | "defaultValue" : null 702 | } ], 703 | "interfaces" : null, 704 | "enumValues" : null, 705 | "possibleTypes" : null 706 | }, { 707 | "kind" : "INPUT_OBJECT", 708 | "name" : "S3ObjectInput", 709 | "description" : null, 710 | "fields" : null, 711 | "inputFields" : [ { 712 | "name" : "bucket", 713 | "description" : null, 714 | "type" : { 715 | "kind" : "NON_NULL", 716 | "name" : null, 717 | "ofType" : { 718 | "kind" : "SCALAR", 719 | "name" : "String", 720 | "ofType" : null 721 | } 722 | }, 723 | "defaultValue" : null 724 | }, { 725 | "name" : "region", 726 | "description" : null, 727 | "type" : { 728 | "kind" : "NON_NULL", 729 | "name" : null, 730 | "ofType" : { 731 | "kind" : "SCALAR", 732 | "name" : "String", 733 | "ofType" : null 734 | } 735 | }, 736 | "defaultValue" : null 737 | }, { 738 | "name" : "key", 739 | "description" : null, 740 | "type" : { 741 | "kind" : "NON_NULL", 742 | "name" : null, 743 | "ofType" : { 744 | "kind" : "SCALAR", 745 | "name" : "String", 746 | "ofType" : null 747 | } 748 | }, 749 | "defaultValue" : null 750 | } ], 751 | "interfaces" : null, 752 | "enumValues" : null, 753 | "possibleTypes" : null 754 | }, { 755 | "kind" : "INPUT_OBJECT", 756 | "name" : "UpdateUserInput", 757 | "description" : null, 758 | "fields" : null, 759 | "inputFields" : [ { 760 | "name" : "id", 761 | "description" : null, 762 | "type" : { 763 | "kind" : "NON_NULL", 764 | "name" : null, 765 | "ofType" : { 766 | "kind" : "SCALAR", 767 | "name" : "ID", 768 | "ofType" : null 769 | } 770 | }, 771 | "defaultValue" : null 772 | }, { 773 | "name" : "username", 774 | "description" : null, 775 | "type" : { 776 | "kind" : "SCALAR", 777 | "name" : "String", 778 | "ofType" : null 779 | }, 780 | "defaultValue" : null 781 | }, { 782 | "name" : "visibility", 783 | "description" : null, 784 | "type" : { 785 | "kind" : "ENUM", 786 | "name" : "Visibility", 787 | "ofType" : null 788 | }, 789 | "defaultValue" : null 790 | }, { 791 | "name" : "avatar", 792 | "description" : null, 793 | "type" : { 794 | "kind" : "INPUT_OBJECT", 795 | "name" : "S3ObjectInput", 796 | "ofType" : null 797 | }, 798 | "defaultValue" : null 799 | } ], 800 | "interfaces" : null, 801 | "enumValues" : null, 802 | "possibleTypes" : null 803 | }, { 804 | "kind" : "INPUT_OBJECT", 805 | "name" : "DeleteUserInput", 806 | "description" : null, 807 | "fields" : null, 808 | "inputFields" : [ { 809 | "name" : "id", 810 | "description" : null, 811 | "type" : { 812 | "kind" : "SCALAR", 813 | "name" : "ID", 814 | "ofType" : null 815 | }, 816 | "defaultValue" : null 817 | } ], 818 | "interfaces" : null, 819 | "enumValues" : null, 820 | "possibleTypes" : null 821 | }, { 822 | "kind" : "OBJECT", 823 | "name" : "Subscription", 824 | "description" : null, 825 | "fields" : [ { 826 | "name" : "onCreateUser", 827 | "description" : null, 828 | "args" : [ ], 829 | "type" : { 830 | "kind" : "OBJECT", 831 | "name" : "User", 832 | "ofType" : null 833 | }, 834 | "isDeprecated" : false, 835 | "deprecationReason" : null 836 | }, { 837 | "name" : "onUpdateUser", 838 | "description" : null, 839 | "args" : [ ], 840 | "type" : { 841 | "kind" : "OBJECT", 842 | "name" : "User", 843 | "ofType" : null 844 | }, 845 | "isDeprecated" : false, 846 | "deprecationReason" : null 847 | }, { 848 | "name" : "onDeleteUser", 849 | "description" : null, 850 | "args" : [ ], 851 | "type" : { 852 | "kind" : "OBJECT", 853 | "name" : "User", 854 | "ofType" : null 855 | }, 856 | "isDeprecated" : false, 857 | "deprecationReason" : null 858 | } ], 859 | "inputFields" : null, 860 | "interfaces" : [ ], 861 | "enumValues" : null, 862 | "possibleTypes" : null 863 | }, { 864 | "kind" : "ENUM", 865 | "name" : "ModelSortDirection", 866 | "description" : null, 867 | "fields" : null, 868 | "inputFields" : null, 869 | "interfaces" : null, 870 | "enumValues" : [ { 871 | "name" : "ASC", 872 | "description" : null, 873 | "isDeprecated" : false, 874 | "deprecationReason" : null 875 | }, { 876 | "name" : "DESC", 877 | "description" : null, 878 | "isDeprecated" : false, 879 | "deprecationReason" : null 880 | } ], 881 | "possibleTypes" : null 882 | }, { 883 | "kind" : "INPUT_OBJECT", 884 | "name" : "ModelBooleanFilterInput", 885 | "description" : null, 886 | "fields" : null, 887 | "inputFields" : [ { 888 | "name" : "ne", 889 | "description" : null, 890 | "type" : { 891 | "kind" : "SCALAR", 892 | "name" : "Boolean", 893 | "ofType" : null 894 | }, 895 | "defaultValue" : null 896 | }, { 897 | "name" : "eq", 898 | "description" : null, 899 | "type" : { 900 | "kind" : "SCALAR", 901 | "name" : "Boolean", 902 | "ofType" : null 903 | }, 904 | "defaultValue" : null 905 | } ], 906 | "interfaces" : null, 907 | "enumValues" : null, 908 | "possibleTypes" : null 909 | }, { 910 | "kind" : "SCALAR", 911 | "name" : "Boolean", 912 | "description" : "Built-in Boolean", 913 | "fields" : null, 914 | "inputFields" : null, 915 | "interfaces" : null, 916 | "enumValues" : null, 917 | "possibleTypes" : null 918 | }, { 919 | "kind" : "INPUT_OBJECT", 920 | "name" : "ModelFloatFilterInput", 921 | "description" : null, 922 | "fields" : null, 923 | "inputFields" : [ { 924 | "name" : "ne", 925 | "description" : null, 926 | "type" : { 927 | "kind" : "SCALAR", 928 | "name" : "Float", 929 | "ofType" : null 930 | }, 931 | "defaultValue" : null 932 | }, { 933 | "name" : "eq", 934 | "description" : null, 935 | "type" : { 936 | "kind" : "SCALAR", 937 | "name" : "Float", 938 | "ofType" : null 939 | }, 940 | "defaultValue" : null 941 | }, { 942 | "name" : "le", 943 | "description" : null, 944 | "type" : { 945 | "kind" : "SCALAR", 946 | "name" : "Float", 947 | "ofType" : null 948 | }, 949 | "defaultValue" : null 950 | }, { 951 | "name" : "lt", 952 | "description" : null, 953 | "type" : { 954 | "kind" : "SCALAR", 955 | "name" : "Float", 956 | "ofType" : null 957 | }, 958 | "defaultValue" : null 959 | }, { 960 | "name" : "ge", 961 | "description" : null, 962 | "type" : { 963 | "kind" : "SCALAR", 964 | "name" : "Float", 965 | "ofType" : null 966 | }, 967 | "defaultValue" : null 968 | }, { 969 | "name" : "gt", 970 | "description" : null, 971 | "type" : { 972 | "kind" : "SCALAR", 973 | "name" : "Float", 974 | "ofType" : null 975 | }, 976 | "defaultValue" : null 977 | }, { 978 | "name" : "contains", 979 | "description" : null, 980 | "type" : { 981 | "kind" : "SCALAR", 982 | "name" : "Float", 983 | "ofType" : null 984 | }, 985 | "defaultValue" : null 986 | }, { 987 | "name" : "notContains", 988 | "description" : null, 989 | "type" : { 990 | "kind" : "SCALAR", 991 | "name" : "Float", 992 | "ofType" : null 993 | }, 994 | "defaultValue" : null 995 | }, { 996 | "name" : "between", 997 | "description" : null, 998 | "type" : { 999 | "kind" : "LIST", 1000 | "name" : null, 1001 | "ofType" : { 1002 | "kind" : "SCALAR", 1003 | "name" : "Float", 1004 | "ofType" : null 1005 | } 1006 | }, 1007 | "defaultValue" : null 1008 | } ], 1009 | "interfaces" : null, 1010 | "enumValues" : null, 1011 | "possibleTypes" : null 1012 | }, { 1013 | "kind" : "SCALAR", 1014 | "name" : "Float", 1015 | "description" : "Built-in Float", 1016 | "fields" : null, 1017 | "inputFields" : null, 1018 | "interfaces" : null, 1019 | "enumValues" : null, 1020 | "possibleTypes" : null 1021 | }, { 1022 | "kind" : "INPUT_OBJECT", 1023 | "name" : "ModelIntFilterInput", 1024 | "description" : null, 1025 | "fields" : null, 1026 | "inputFields" : [ { 1027 | "name" : "ne", 1028 | "description" : null, 1029 | "type" : { 1030 | "kind" : "SCALAR", 1031 | "name" : "Int", 1032 | "ofType" : null 1033 | }, 1034 | "defaultValue" : null 1035 | }, { 1036 | "name" : "eq", 1037 | "description" : null, 1038 | "type" : { 1039 | "kind" : "SCALAR", 1040 | "name" : "Int", 1041 | "ofType" : null 1042 | }, 1043 | "defaultValue" : null 1044 | }, { 1045 | "name" : "le", 1046 | "description" : null, 1047 | "type" : { 1048 | "kind" : "SCALAR", 1049 | "name" : "Int", 1050 | "ofType" : null 1051 | }, 1052 | "defaultValue" : null 1053 | }, { 1054 | "name" : "lt", 1055 | "description" : null, 1056 | "type" : { 1057 | "kind" : "SCALAR", 1058 | "name" : "Int", 1059 | "ofType" : null 1060 | }, 1061 | "defaultValue" : null 1062 | }, { 1063 | "name" : "ge", 1064 | "description" : null, 1065 | "type" : { 1066 | "kind" : "SCALAR", 1067 | "name" : "Int", 1068 | "ofType" : null 1069 | }, 1070 | "defaultValue" : null 1071 | }, { 1072 | "name" : "gt", 1073 | "description" : null, 1074 | "type" : { 1075 | "kind" : "SCALAR", 1076 | "name" : "Int", 1077 | "ofType" : null 1078 | }, 1079 | "defaultValue" : null 1080 | }, { 1081 | "name" : "contains", 1082 | "description" : null, 1083 | "type" : { 1084 | "kind" : "SCALAR", 1085 | "name" : "Int", 1086 | "ofType" : null 1087 | }, 1088 | "defaultValue" : null 1089 | }, { 1090 | "name" : "notContains", 1091 | "description" : null, 1092 | "type" : { 1093 | "kind" : "SCALAR", 1094 | "name" : "Int", 1095 | "ofType" : null 1096 | }, 1097 | "defaultValue" : null 1098 | }, { 1099 | "name" : "between", 1100 | "description" : null, 1101 | "type" : { 1102 | "kind" : "LIST", 1103 | "name" : null, 1104 | "ofType" : { 1105 | "kind" : "SCALAR", 1106 | "name" : "Int", 1107 | "ofType" : null 1108 | } 1109 | }, 1110 | "defaultValue" : null 1111 | } ], 1112 | "interfaces" : null, 1113 | "enumValues" : null, 1114 | "possibleTypes" : null 1115 | }, { 1116 | "kind" : "OBJECT", 1117 | "name" : "__Schema", 1118 | "description" : "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.", 1119 | "fields" : [ { 1120 | "name" : "types", 1121 | "description" : "A list of all types supported by this server.", 1122 | "args" : [ ], 1123 | "type" : { 1124 | "kind" : "NON_NULL", 1125 | "name" : null, 1126 | "ofType" : { 1127 | "kind" : "LIST", 1128 | "name" : null, 1129 | "ofType" : { 1130 | "kind" : "NON_NULL", 1131 | "name" : null, 1132 | "ofType" : { 1133 | "kind" : "OBJECT", 1134 | "name" : "__Type", 1135 | "ofType" : null 1136 | } 1137 | } 1138 | } 1139 | }, 1140 | "isDeprecated" : false, 1141 | "deprecationReason" : null 1142 | }, { 1143 | "name" : "queryType", 1144 | "description" : "The type that query operations will be rooted at.", 1145 | "args" : [ ], 1146 | "type" : { 1147 | "kind" : "NON_NULL", 1148 | "name" : null, 1149 | "ofType" : { 1150 | "kind" : "OBJECT", 1151 | "name" : "__Type", 1152 | "ofType" : null 1153 | } 1154 | }, 1155 | "isDeprecated" : false, 1156 | "deprecationReason" : null 1157 | }, { 1158 | "name" : "mutationType", 1159 | "description" : "If this server supports mutation, the type that mutation operations will be rooted at.", 1160 | "args" : [ ], 1161 | "type" : { 1162 | "kind" : "OBJECT", 1163 | "name" : "__Type", 1164 | "ofType" : null 1165 | }, 1166 | "isDeprecated" : false, 1167 | "deprecationReason" : null 1168 | }, { 1169 | "name" : "directives", 1170 | "description" : "'A list of all directives supported by this server.", 1171 | "args" : [ ], 1172 | "type" : { 1173 | "kind" : "NON_NULL", 1174 | "name" : null, 1175 | "ofType" : { 1176 | "kind" : "LIST", 1177 | "name" : null, 1178 | "ofType" : { 1179 | "kind" : "NON_NULL", 1180 | "name" : null, 1181 | "ofType" : { 1182 | "kind" : "OBJECT", 1183 | "name" : "__Directive", 1184 | "ofType" : null 1185 | } 1186 | } 1187 | } 1188 | }, 1189 | "isDeprecated" : false, 1190 | "deprecationReason" : null 1191 | }, { 1192 | "name" : "subscriptionType", 1193 | "description" : "'If this server support subscription, the type that subscription operations will be rooted at.", 1194 | "args" : [ ], 1195 | "type" : { 1196 | "kind" : "OBJECT", 1197 | "name" : "__Type", 1198 | "ofType" : null 1199 | }, 1200 | "isDeprecated" : false, 1201 | "deprecationReason" : null 1202 | } ], 1203 | "inputFields" : null, 1204 | "interfaces" : [ ], 1205 | "enumValues" : null, 1206 | "possibleTypes" : null 1207 | }, { 1208 | "kind" : "OBJECT", 1209 | "name" : "__Type", 1210 | "description" : null, 1211 | "fields" : [ { 1212 | "name" : "kind", 1213 | "description" : null, 1214 | "args" : [ ], 1215 | "type" : { 1216 | "kind" : "NON_NULL", 1217 | "name" : null, 1218 | "ofType" : { 1219 | "kind" : "ENUM", 1220 | "name" : "__TypeKind", 1221 | "ofType" : null 1222 | } 1223 | }, 1224 | "isDeprecated" : false, 1225 | "deprecationReason" : null 1226 | }, { 1227 | "name" : "name", 1228 | "description" : null, 1229 | "args" : [ ], 1230 | "type" : { 1231 | "kind" : "SCALAR", 1232 | "name" : "String", 1233 | "ofType" : null 1234 | }, 1235 | "isDeprecated" : false, 1236 | "deprecationReason" : null 1237 | }, { 1238 | "name" : "description", 1239 | "description" : null, 1240 | "args" : [ ], 1241 | "type" : { 1242 | "kind" : "SCALAR", 1243 | "name" : "String", 1244 | "ofType" : null 1245 | }, 1246 | "isDeprecated" : false, 1247 | "deprecationReason" : null 1248 | }, { 1249 | "name" : "fields", 1250 | "description" : null, 1251 | "args" : [ { 1252 | "name" : "includeDeprecated", 1253 | "description" : null, 1254 | "type" : { 1255 | "kind" : "SCALAR", 1256 | "name" : "Boolean", 1257 | "ofType" : null 1258 | }, 1259 | "defaultValue" : "false" 1260 | } ], 1261 | "type" : { 1262 | "kind" : "LIST", 1263 | "name" : null, 1264 | "ofType" : { 1265 | "kind" : "NON_NULL", 1266 | "name" : null, 1267 | "ofType" : { 1268 | "kind" : "OBJECT", 1269 | "name" : "__Field", 1270 | "ofType" : null 1271 | } 1272 | } 1273 | }, 1274 | "isDeprecated" : false, 1275 | "deprecationReason" : null 1276 | }, { 1277 | "name" : "interfaces", 1278 | "description" : null, 1279 | "args" : [ ], 1280 | "type" : { 1281 | "kind" : "LIST", 1282 | "name" : null, 1283 | "ofType" : { 1284 | "kind" : "NON_NULL", 1285 | "name" : null, 1286 | "ofType" : { 1287 | "kind" : "OBJECT", 1288 | "name" : "__Type", 1289 | "ofType" : null 1290 | } 1291 | } 1292 | }, 1293 | "isDeprecated" : false, 1294 | "deprecationReason" : null 1295 | }, { 1296 | "name" : "possibleTypes", 1297 | "description" : null, 1298 | "args" : [ ], 1299 | "type" : { 1300 | "kind" : "LIST", 1301 | "name" : null, 1302 | "ofType" : { 1303 | "kind" : "NON_NULL", 1304 | "name" : null, 1305 | "ofType" : { 1306 | "kind" : "OBJECT", 1307 | "name" : "__Type", 1308 | "ofType" : null 1309 | } 1310 | } 1311 | }, 1312 | "isDeprecated" : false, 1313 | "deprecationReason" : null 1314 | }, { 1315 | "name" : "enumValues", 1316 | "description" : null, 1317 | "args" : [ { 1318 | "name" : "includeDeprecated", 1319 | "description" : null, 1320 | "type" : { 1321 | "kind" : "SCALAR", 1322 | "name" : "Boolean", 1323 | "ofType" : null 1324 | }, 1325 | "defaultValue" : "false" 1326 | } ], 1327 | "type" : { 1328 | "kind" : "LIST", 1329 | "name" : null, 1330 | "ofType" : { 1331 | "kind" : "NON_NULL", 1332 | "name" : null, 1333 | "ofType" : { 1334 | "kind" : "OBJECT", 1335 | "name" : "__EnumValue", 1336 | "ofType" : null 1337 | } 1338 | } 1339 | }, 1340 | "isDeprecated" : false, 1341 | "deprecationReason" : null 1342 | }, { 1343 | "name" : "inputFields", 1344 | "description" : null, 1345 | "args" : [ ], 1346 | "type" : { 1347 | "kind" : "LIST", 1348 | "name" : null, 1349 | "ofType" : { 1350 | "kind" : "NON_NULL", 1351 | "name" : null, 1352 | "ofType" : { 1353 | "kind" : "OBJECT", 1354 | "name" : "__InputValue", 1355 | "ofType" : null 1356 | } 1357 | } 1358 | }, 1359 | "isDeprecated" : false, 1360 | "deprecationReason" : null 1361 | }, { 1362 | "name" : "ofType", 1363 | "description" : null, 1364 | "args" : [ ], 1365 | "type" : { 1366 | "kind" : "OBJECT", 1367 | "name" : "__Type", 1368 | "ofType" : null 1369 | }, 1370 | "isDeprecated" : false, 1371 | "deprecationReason" : null 1372 | } ], 1373 | "inputFields" : null, 1374 | "interfaces" : [ ], 1375 | "enumValues" : null, 1376 | "possibleTypes" : null 1377 | }, { 1378 | "kind" : "ENUM", 1379 | "name" : "__TypeKind", 1380 | "description" : "An enum describing what kind of type a given __Type is", 1381 | "fields" : null, 1382 | "inputFields" : null, 1383 | "interfaces" : null, 1384 | "enumValues" : [ { 1385 | "name" : "SCALAR", 1386 | "description" : "Indicates this type is a scalar.", 1387 | "isDeprecated" : false, 1388 | "deprecationReason" : null 1389 | }, { 1390 | "name" : "OBJECT", 1391 | "description" : "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 1392 | "isDeprecated" : false, 1393 | "deprecationReason" : null 1394 | }, { 1395 | "name" : "INTERFACE", 1396 | "description" : "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 1397 | "isDeprecated" : false, 1398 | "deprecationReason" : null 1399 | }, { 1400 | "name" : "UNION", 1401 | "description" : "Indicates this type is a union. `possibleTypes` is a valid field.", 1402 | "isDeprecated" : false, 1403 | "deprecationReason" : null 1404 | }, { 1405 | "name" : "ENUM", 1406 | "description" : "Indicates this type is an enum. `enumValues` is a valid field.", 1407 | "isDeprecated" : false, 1408 | "deprecationReason" : null 1409 | }, { 1410 | "name" : "INPUT_OBJECT", 1411 | "description" : "Indicates this type is an input object. `inputFields` is a valid field.", 1412 | "isDeprecated" : false, 1413 | "deprecationReason" : null 1414 | }, { 1415 | "name" : "LIST", 1416 | "description" : "Indicates this type is a list. `ofType` is a valid field.", 1417 | "isDeprecated" : false, 1418 | "deprecationReason" : null 1419 | }, { 1420 | "name" : "NON_NULL", 1421 | "description" : "Indicates this type is a non-null. `ofType` is a valid field.", 1422 | "isDeprecated" : false, 1423 | "deprecationReason" : null 1424 | } ], 1425 | "possibleTypes" : null 1426 | }, { 1427 | "kind" : "OBJECT", 1428 | "name" : "__Field", 1429 | "description" : null, 1430 | "fields" : [ { 1431 | "name" : "name", 1432 | "description" : null, 1433 | "args" : [ ], 1434 | "type" : { 1435 | "kind" : "NON_NULL", 1436 | "name" : null, 1437 | "ofType" : { 1438 | "kind" : "SCALAR", 1439 | "name" : "String", 1440 | "ofType" : null 1441 | } 1442 | }, 1443 | "isDeprecated" : false, 1444 | "deprecationReason" : null 1445 | }, { 1446 | "name" : "description", 1447 | "description" : null, 1448 | "args" : [ ], 1449 | "type" : { 1450 | "kind" : "SCALAR", 1451 | "name" : "String", 1452 | "ofType" : null 1453 | }, 1454 | "isDeprecated" : false, 1455 | "deprecationReason" : null 1456 | }, { 1457 | "name" : "args", 1458 | "description" : null, 1459 | "args" : [ ], 1460 | "type" : { 1461 | "kind" : "NON_NULL", 1462 | "name" : null, 1463 | "ofType" : { 1464 | "kind" : "LIST", 1465 | "name" : null, 1466 | "ofType" : { 1467 | "kind" : "NON_NULL", 1468 | "name" : null, 1469 | "ofType" : { 1470 | "kind" : "OBJECT", 1471 | "name" : "__InputValue", 1472 | "ofType" : null 1473 | } 1474 | } 1475 | } 1476 | }, 1477 | "isDeprecated" : false, 1478 | "deprecationReason" : null 1479 | }, { 1480 | "name" : "type", 1481 | "description" : null, 1482 | "args" : [ ], 1483 | "type" : { 1484 | "kind" : "NON_NULL", 1485 | "name" : null, 1486 | "ofType" : { 1487 | "kind" : "OBJECT", 1488 | "name" : "__Type", 1489 | "ofType" : null 1490 | } 1491 | }, 1492 | "isDeprecated" : false, 1493 | "deprecationReason" : null 1494 | }, { 1495 | "name" : "isDeprecated", 1496 | "description" : null, 1497 | "args" : [ ], 1498 | "type" : { 1499 | "kind" : "NON_NULL", 1500 | "name" : null, 1501 | "ofType" : { 1502 | "kind" : "SCALAR", 1503 | "name" : "Boolean", 1504 | "ofType" : null 1505 | } 1506 | }, 1507 | "isDeprecated" : false, 1508 | "deprecationReason" : null 1509 | }, { 1510 | "name" : "deprecationReason", 1511 | "description" : null, 1512 | "args" : [ ], 1513 | "type" : { 1514 | "kind" : "SCALAR", 1515 | "name" : "String", 1516 | "ofType" : null 1517 | }, 1518 | "isDeprecated" : false, 1519 | "deprecationReason" : null 1520 | } ], 1521 | "inputFields" : null, 1522 | "interfaces" : [ ], 1523 | "enumValues" : null, 1524 | "possibleTypes" : null 1525 | }, { 1526 | "kind" : "OBJECT", 1527 | "name" : "__InputValue", 1528 | "description" : null, 1529 | "fields" : [ { 1530 | "name" : "name", 1531 | "description" : null, 1532 | "args" : [ ], 1533 | "type" : { 1534 | "kind" : "NON_NULL", 1535 | "name" : null, 1536 | "ofType" : { 1537 | "kind" : "SCALAR", 1538 | "name" : "String", 1539 | "ofType" : null 1540 | } 1541 | }, 1542 | "isDeprecated" : false, 1543 | "deprecationReason" : null 1544 | }, { 1545 | "name" : "description", 1546 | "description" : null, 1547 | "args" : [ ], 1548 | "type" : { 1549 | "kind" : "SCALAR", 1550 | "name" : "String", 1551 | "ofType" : null 1552 | }, 1553 | "isDeprecated" : false, 1554 | "deprecationReason" : null 1555 | }, { 1556 | "name" : "type", 1557 | "description" : null, 1558 | "args" : [ ], 1559 | "type" : { 1560 | "kind" : "NON_NULL", 1561 | "name" : null, 1562 | "ofType" : { 1563 | "kind" : "OBJECT", 1564 | "name" : "__Type", 1565 | "ofType" : null 1566 | } 1567 | }, 1568 | "isDeprecated" : false, 1569 | "deprecationReason" : null 1570 | }, { 1571 | "name" : "defaultValue", 1572 | "description" : null, 1573 | "args" : [ ], 1574 | "type" : { 1575 | "kind" : "SCALAR", 1576 | "name" : "String", 1577 | "ofType" : null 1578 | }, 1579 | "isDeprecated" : false, 1580 | "deprecationReason" : null 1581 | } ], 1582 | "inputFields" : null, 1583 | "interfaces" : [ ], 1584 | "enumValues" : null, 1585 | "possibleTypes" : null 1586 | }, { 1587 | "kind" : "OBJECT", 1588 | "name" : "__EnumValue", 1589 | "description" : null, 1590 | "fields" : [ { 1591 | "name" : "name", 1592 | "description" : null, 1593 | "args" : [ ], 1594 | "type" : { 1595 | "kind" : "NON_NULL", 1596 | "name" : null, 1597 | "ofType" : { 1598 | "kind" : "SCALAR", 1599 | "name" : "String", 1600 | "ofType" : null 1601 | } 1602 | }, 1603 | "isDeprecated" : false, 1604 | "deprecationReason" : null 1605 | }, { 1606 | "name" : "description", 1607 | "description" : null, 1608 | "args" : [ ], 1609 | "type" : { 1610 | "kind" : "SCALAR", 1611 | "name" : "String", 1612 | "ofType" : null 1613 | }, 1614 | "isDeprecated" : false, 1615 | "deprecationReason" : null 1616 | }, { 1617 | "name" : "isDeprecated", 1618 | "description" : null, 1619 | "args" : [ ], 1620 | "type" : { 1621 | "kind" : "NON_NULL", 1622 | "name" : null, 1623 | "ofType" : { 1624 | "kind" : "SCALAR", 1625 | "name" : "Boolean", 1626 | "ofType" : null 1627 | } 1628 | }, 1629 | "isDeprecated" : false, 1630 | "deprecationReason" : null 1631 | }, { 1632 | "name" : "deprecationReason", 1633 | "description" : null, 1634 | "args" : [ ], 1635 | "type" : { 1636 | "kind" : "SCALAR", 1637 | "name" : "String", 1638 | "ofType" : null 1639 | }, 1640 | "isDeprecated" : false, 1641 | "deprecationReason" : null 1642 | } ], 1643 | "inputFields" : null, 1644 | "interfaces" : [ ], 1645 | "enumValues" : null, 1646 | "possibleTypes" : null 1647 | }, { 1648 | "kind" : "OBJECT", 1649 | "name" : "__Directive", 1650 | "description" : null, 1651 | "fields" : [ { 1652 | "name" : "name", 1653 | "description" : null, 1654 | "args" : [ ], 1655 | "type" : { 1656 | "kind" : "SCALAR", 1657 | "name" : "String", 1658 | "ofType" : null 1659 | }, 1660 | "isDeprecated" : false, 1661 | "deprecationReason" : null 1662 | }, { 1663 | "name" : "description", 1664 | "description" : null, 1665 | "args" : [ ], 1666 | "type" : { 1667 | "kind" : "SCALAR", 1668 | "name" : "String", 1669 | "ofType" : null 1670 | }, 1671 | "isDeprecated" : false, 1672 | "deprecationReason" : null 1673 | }, { 1674 | "name" : "locations", 1675 | "description" : null, 1676 | "args" : [ ], 1677 | "type" : { 1678 | "kind" : "LIST", 1679 | "name" : null, 1680 | "ofType" : { 1681 | "kind" : "NON_NULL", 1682 | "name" : null, 1683 | "ofType" : { 1684 | "kind" : "ENUM", 1685 | "name" : "__DirectiveLocation", 1686 | "ofType" : null 1687 | } 1688 | } 1689 | }, 1690 | "isDeprecated" : false, 1691 | "deprecationReason" : null 1692 | }, { 1693 | "name" : "args", 1694 | "description" : null, 1695 | "args" : [ ], 1696 | "type" : { 1697 | "kind" : "NON_NULL", 1698 | "name" : null, 1699 | "ofType" : { 1700 | "kind" : "LIST", 1701 | "name" : null, 1702 | "ofType" : { 1703 | "kind" : "NON_NULL", 1704 | "name" : null, 1705 | "ofType" : { 1706 | "kind" : "OBJECT", 1707 | "name" : "__InputValue", 1708 | "ofType" : null 1709 | } 1710 | } 1711 | } 1712 | }, 1713 | "isDeprecated" : false, 1714 | "deprecationReason" : null 1715 | }, { 1716 | "name" : "onOperation", 1717 | "description" : null, 1718 | "args" : [ ], 1719 | "type" : { 1720 | "kind" : "SCALAR", 1721 | "name" : "Boolean", 1722 | "ofType" : null 1723 | }, 1724 | "isDeprecated" : true, 1725 | "deprecationReason" : "Use `locations`." 1726 | }, { 1727 | "name" : "onFragment", 1728 | "description" : null, 1729 | "args" : [ ], 1730 | "type" : { 1731 | "kind" : "SCALAR", 1732 | "name" : "Boolean", 1733 | "ofType" : null 1734 | }, 1735 | "isDeprecated" : true, 1736 | "deprecationReason" : "Use `locations`." 1737 | }, { 1738 | "name" : "onField", 1739 | "description" : null, 1740 | "args" : [ ], 1741 | "type" : { 1742 | "kind" : "SCALAR", 1743 | "name" : "Boolean", 1744 | "ofType" : null 1745 | }, 1746 | "isDeprecated" : true, 1747 | "deprecationReason" : "Use `locations`." 1748 | } ], 1749 | "inputFields" : null, 1750 | "interfaces" : [ ], 1751 | "enumValues" : null, 1752 | "possibleTypes" : null 1753 | }, { 1754 | "kind" : "ENUM", 1755 | "name" : "__DirectiveLocation", 1756 | "description" : "An enum describing valid locations where a directive can be placed", 1757 | "fields" : null, 1758 | "inputFields" : null, 1759 | "interfaces" : null, 1760 | "enumValues" : [ { 1761 | "name" : "QUERY", 1762 | "description" : "Indicates the directive is valid on queries.", 1763 | "isDeprecated" : false, 1764 | "deprecationReason" : null 1765 | }, { 1766 | "name" : "MUTATION", 1767 | "description" : "Indicates the directive is valid on mutations.", 1768 | "isDeprecated" : false, 1769 | "deprecationReason" : null 1770 | }, { 1771 | "name" : "FIELD", 1772 | "description" : "Indicates the directive is valid on fields.", 1773 | "isDeprecated" : false, 1774 | "deprecationReason" : null 1775 | }, { 1776 | "name" : "FRAGMENT_DEFINITION", 1777 | "description" : "Indicates the directive is valid on fragment definitions.", 1778 | "isDeprecated" : false, 1779 | "deprecationReason" : null 1780 | }, { 1781 | "name" : "FRAGMENT_SPREAD", 1782 | "description" : "Indicates the directive is valid on fragment spreads.", 1783 | "isDeprecated" : false, 1784 | "deprecationReason" : null 1785 | }, { 1786 | "name" : "INLINE_FRAGMENT", 1787 | "description" : "Indicates the directive is valid on inline fragments.", 1788 | "isDeprecated" : false, 1789 | "deprecationReason" : null 1790 | }, { 1791 | "name" : "SCHEMA", 1792 | "description" : "Indicates the directive is valid on a schema SDL definition.", 1793 | "isDeprecated" : false, 1794 | "deprecationReason" : null 1795 | }, { 1796 | "name" : "SCALAR", 1797 | "description" : "Indicates the directive is valid on a scalar SDL definition.", 1798 | "isDeprecated" : false, 1799 | "deprecationReason" : null 1800 | }, { 1801 | "name" : "OBJECT", 1802 | "description" : "Indicates the directive is valid on an object SDL definition.", 1803 | "isDeprecated" : false, 1804 | "deprecationReason" : null 1805 | }, { 1806 | "name" : "FIELD_DEFINITION", 1807 | "description" : "Indicates the directive is valid on a field SDL definition.", 1808 | "isDeprecated" : false, 1809 | "deprecationReason" : null 1810 | }, { 1811 | "name" : "ARGUMENT_DEFINITION", 1812 | "description" : "Indicates the directive is valid on a field argument SDL definition.", 1813 | "isDeprecated" : false, 1814 | "deprecationReason" : null 1815 | }, { 1816 | "name" : "INTERFACE", 1817 | "description" : "Indicates the directive is valid on an interface SDL definition.", 1818 | "isDeprecated" : false, 1819 | "deprecationReason" : null 1820 | }, { 1821 | "name" : "UNION", 1822 | "description" : "Indicates the directive is valid on an union SDL definition.", 1823 | "isDeprecated" : false, 1824 | "deprecationReason" : null 1825 | }, { 1826 | "name" : "ENUM", 1827 | "description" : "Indicates the directive is valid on an enum SDL definition.", 1828 | "isDeprecated" : false, 1829 | "deprecationReason" : null 1830 | }, { 1831 | "name" : "ENUM_VALUE", 1832 | "description" : "Indicates the directive is valid on an enum value SDL definition.", 1833 | "isDeprecated" : false, 1834 | "deprecationReason" : null 1835 | }, { 1836 | "name" : "INPUT_OBJECT", 1837 | "description" : "Indicates the directive is valid on an input object SDL definition.", 1838 | "isDeprecated" : false, 1839 | "deprecationReason" : null 1840 | }, { 1841 | "name" : "INPUT_FIELD_DEFINITION", 1842 | "description" : "Indicates the directive is valid on an input object field SDL definition.", 1843 | "isDeprecated" : false, 1844 | "deprecationReason" : null 1845 | } ], 1846 | "possibleTypes" : null 1847 | } ], 1848 | "directives" : [ { 1849 | "name" : "include", 1850 | "description" : "Directs the executor to include this field or fragment only when the `if` argument is true", 1851 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1852 | "args" : [ { 1853 | "name" : "if", 1854 | "description" : "Included when true.", 1855 | "type" : { 1856 | "kind" : "NON_NULL", 1857 | "name" : null, 1858 | "ofType" : { 1859 | "kind" : "SCALAR", 1860 | "name" : "Boolean", 1861 | "ofType" : null 1862 | } 1863 | }, 1864 | "defaultValue" : null 1865 | } ], 1866 | "onOperation" : false, 1867 | "onFragment" : true, 1868 | "onField" : true 1869 | }, { 1870 | "name" : "skip", 1871 | "description" : "Directs the executor to skip this field or fragment when the `if`'argument is true.", 1872 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1873 | "args" : [ { 1874 | "name" : "if", 1875 | "description" : "Skipped when true.", 1876 | "type" : { 1877 | "kind" : "NON_NULL", 1878 | "name" : null, 1879 | "ofType" : { 1880 | "kind" : "SCALAR", 1881 | "name" : "Boolean", 1882 | "ofType" : null 1883 | } 1884 | }, 1885 | "defaultValue" : null 1886 | } ], 1887 | "onOperation" : false, 1888 | "onFragment" : true, 1889 | "onField" : true 1890 | }, { 1891 | "name" : "defer", 1892 | "description" : "This directive allows results to be deferred during execution", 1893 | "locations" : [ "FIELD" ], 1894 | "args" : [ ], 1895 | "onOperation" : false, 1896 | "onFragment" : false, 1897 | "onField" : true 1898 | }, { 1899 | "name" : "deprecated", 1900 | "description" : null, 1901 | "locations" : [ "FIELD_DEFINITION", "ENUM_VALUE" ], 1902 | "args" : [ { 1903 | "name" : "reason", 1904 | "description" : null, 1905 | "type" : { 1906 | "kind" : "SCALAR", 1907 | "name" : "String", 1908 | "ofType" : null 1909 | }, 1910 | "defaultValue" : "\"No longer supported\"" 1911 | } ], 1912 | "onOperation" : false, 1913 | "onFragment" : false, 1914 | "onField" : false 1915 | }, { 1916 | "name" : "aws_auth", 1917 | "description" : "Directs the schema to enforce authorization on a field", 1918 | "locations" : [ "FIELD_DEFINITION" ], 1919 | "args" : [ { 1920 | "name" : "cognito_groups", 1921 | "description" : "List of cognito user pool groups which have access on this field", 1922 | "type" : { 1923 | "kind" : "LIST", 1924 | "name" : null, 1925 | "ofType" : { 1926 | "kind" : "SCALAR", 1927 | "name" : "String", 1928 | "ofType" : null 1929 | } 1930 | }, 1931 | "defaultValue" : null 1932 | } ], 1933 | "onOperation" : false, 1934 | "onFragment" : false, 1935 | "onField" : false 1936 | }, { 1937 | "name" : "aws_oidc", 1938 | "description" : "Tells the service this field/object has access authorized by an OIDC token.", 1939 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1940 | "args" : [ ], 1941 | "onOperation" : false, 1942 | "onFragment" : false, 1943 | "onField" : false 1944 | }, { 1945 | "name" : "aws_cognito_user_pools", 1946 | "description" : "Tells the service this field/object has access authorized by a Cognito User Pools token.", 1947 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 1948 | "args" : [ { 1949 | "name" : "cognito_groups", 1950 | "description" : "List of cognito user pool groups which have access on this field", 1951 | "type" : { 1952 | "kind" : "LIST", 1953 | "name" : null, 1954 | "ofType" : { 1955 | "kind" : "SCALAR", 1956 | "name" : "String", 1957 | "ofType" : null 1958 | } 1959 | }, 1960 | "defaultValue" : null 1961 | } ], 1962 | "onOperation" : false, 1963 | "onFragment" : false, 1964 | "onField" : false 1965 | }, { 1966 | "name" : "aws_publish", 1967 | "description" : "Tells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.", 1968 | "locations" : [ "FIELD_DEFINITION" ], 1969 | "args" : [ { 1970 | "name" : "subscriptions", 1971 | "description" : "List of subscriptions which will be published to when this mutation is called.", 1972 | "type" : { 1973 | "kind" : "LIST", 1974 | "name" : null, 1975 | "ofType" : { 1976 | "kind" : "SCALAR", 1977 | "name" : "String", 1978 | "ofType" : null 1979 | } 1980 | }, 1981 | "defaultValue" : null 1982 | } ], 1983 | "onOperation" : false, 1984 | "onFragment" : false, 1985 | "onField" : false 1986 | }, { 1987 | "name" : "aws_subscribe", 1988 | "description" : "Tells the service which mutation triggers this subscription.", 1989 | "locations" : [ "FIELD_DEFINITION" ], 1990 | "args" : [ { 1991 | "name" : "mutations", 1992 | "description" : "List of mutations which will trigger this subscription when they are called.", 1993 | "type" : { 1994 | "kind" : "LIST", 1995 | "name" : null, 1996 | "ofType" : { 1997 | "kind" : "SCALAR", 1998 | "name" : "String", 1999 | "ofType" : null 2000 | } 2001 | }, 2002 | "defaultValue" : null 2003 | } ], 2004 | "onOperation" : false, 2005 | "onFragment" : false, 2006 | "onField" : false 2007 | }, { 2008 | "name" : "aws_api_key", 2009 | "description" : "Tells the service this field/object has access authorized by an API key.", 2010 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 2011 | "args" : [ ], 2012 | "onOperation" : false, 2013 | "onFragment" : false, 2014 | "onField" : false 2015 | }, { 2016 | "name" : "aws_iam", 2017 | "description" : "Tells the service this field/object has access authorized by sigv4 signing.", 2018 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ], 2019 | "args" : [ ], 2020 | "onOperation" : false, 2021 | "onFragment" : false, 2022 | "onField" : false 2023 | } ] 2024 | } 2025 | } 2026 | } -------------------------------------------------------------------------------- /src/graphql/subscriptions.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const onCreateUser = `subscription OnCreateUser { 5 | onCreateUser { 6 | id 7 | username 8 | visibility 9 | avatar { 10 | bucket 11 | region 12 | key 13 | } 14 | } 15 | } 16 | `; 17 | export const onUpdateUser = `subscription OnUpdateUser { 18 | onUpdateUser { 19 | id 20 | username 21 | visibility 22 | avatar { 23 | bucket 24 | region 25 | key 26 | } 27 | } 28 | } 29 | `; 30 | export const onDeleteUser = `subscription OnDeleteUser { 31 | onDeleteUser { 32 | id 33 | username 34 | visibility 35 | avatar { 36 | bucket 37 | region 38 | key 39 | } 40 | } 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | import Amplify from 'aws-amplify' 8 | import config from './aws-exports' 9 | Amplify.configure(config) 10 | 11 | ReactDOM.render(, document.getElementById('root')); 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: https://bit.ly/CRA-PWA 16 | serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------