├── .gitignore
├── .graphqlconfig.yml
├── .vscode
└── settings.json
├── README.md
├── amplify
├── .config
│ └── project-config.json
├── backend
│ ├── api
│ │ └── photoalbums
│ │ │ ├── parameters.json
│ │ │ ├── schema.graphql
│ │ │ ├── stacks
│ │ │ └── CustomResources.json
│ │ │ └── transform.conf.json
│ ├── auth
│ │ └── photoalbums9240b8c0
│ │ │ ├── parameters.json
│ │ │ └── photoalbums9240b8c0-cloudformation-template.yml
│ ├── backend-config.json
│ ├── function
│ │ └── S3Triggerb01fd26e
│ │ │ ├── S3Triggerb01fd26e-cloudformation-template.json
│ │ │ └── src
│ │ │ ├── event.json
│ │ │ ├── index.js
│ │ │ ├── package-lock.json
│ │ │ └── package.json
│ ├── hosting
│ │ └── S3AndCloudFront
│ │ │ ├── parameters.json
│ │ │ └── template.json
│ └── storage
│ │ └── photoalbumsstorage
│ │ ├── parameters.json
│ │ ├── s3-cloudformation-template.json
│ │ └── storage-params.json
└── team-provider-info.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── 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
└── setupTests.js
/.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/\#current-cloud-backend
27 | amplify/.config/local-*
28 | amplify/mock-data
29 | amplify/backend/amplify-meta.json
30 | amplify/backend/awscloudformation
31 | build/
32 | dist/
33 | node_modules/
34 | aws-exports.js
35 | awsconfiguration.json
36 | amplifyconfiguration.json
37 | amplify-gradle-config.json
38 | amplifyxc.config
--------------------------------------------------------------------------------
/.graphqlconfig.yml:
--------------------------------------------------------------------------------
1 | projects:
2 | photoalbums:
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 | extensions:
14 | amplify:
15 | version: 3
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | - [Goals](#goals)
2 | - [Install & configure AWS amplify:](#install-amp-configure-aws-amplify)
3 | - [Setup a React app with CRA](#setup-a-react-app-with-cra)
4 | - [Add Semantic UI React](#add-semantic-ui-react)
5 | - [Replace placeholder UI with our own](#replace-placeholder-ui-with-our-own)
6 | - [Initialize Amplify](#initialize-amplify)
7 | - [Add a GraphQL API with Amplify](#add-a-graphql-api-with-amplify)
8 | - [Add an AWS AppSync API](#add-an-aws-appsync-api)
9 | - [Manage Albums UI](#manage-albums-ui)
10 | - [Add Authentication](#add-authentication)
11 | - [Setup the auth front-end](#setup-the-auth-front-end)
12 | - [Recap : What we changed in App.js](#recap--what-we-changed-in-appjs)
13 | - [Try it out : Create an account](#try-it-out--create-an-account)
14 | - [Connect the app to the AppSync API](#connect-the-app-to-the-appsync-api)
15 | - [Allow users to create albums](#allow-users-to-create-albums)
16 | - [Show a live list of albums](#show-a-live-list-of-albums)
17 | - [Allow users to click into an album to view its details](#allow-users-to-click-into-an-album-to-view-its-details)
18 | - [Try out the app](#try-out-the-app)
19 | - [Add Cloud Storage](#add-cloud-storage)
20 | - [Manage photos](#manage-photos)
21 | - [Refactor : move auth to React Context](#refactor--move-auth-to-react-context)
22 | - [Extract labels from images using AI](#extract-labels-from-images-using-ai)
23 | - [Store labels in db](#store-labels-in-db)
24 | - [Deploy](#deploy)
25 |
26 | # Goals
27 |
28 | In this workshop, we’ll build an app with quite a few features, including:
29 |
30 | - Allowing user sign up and authentication, so we know who owns which photo albums
31 |
32 | - Building an API server, so our front end has a way to load the appropriate albums and photos to show a given user
33 |
34 | - Storing data about albums, photos, and permissions of who can view what, so that our API has a fast and reliable place to query and save data to
35 |
36 | - Storing and serving photos, so we have a place to put all of the photos that users are uploading to albums
37 |
38 | - Automatically detecting relevant labels for each uploaded photo and storing them.
39 |
40 | # Install & configure AWS amplify:
41 |
42 | ```sh
43 | npm install -g @aws-amplify/cli
44 |
45 | amplify configure
46 | ```
47 |
48 | More info on getting started [here](https://aws-amplify.github.io/docs/cli-toolchain/quickstart?sdk=js).
49 |
50 | # Setup a React app with CRA
51 |
52 | ```
53 | npx create-react-app photoalbums --use-npm; say done
54 |
55 | cd photoalbums
56 |
57 | git init
58 |
59 | git add --all
60 |
61 | git commit -m "initial react app with CRA"
62 |
63 | npm start
64 | ```
65 |
66 | ## Add Semantic UI React
67 |
68 | Semantic UI components for React provide components that will help us quickly build a nice UI interface.
69 |
70 | ```sh
71 | npm i semantic-ui-react
72 | ```
73 |
74 | In `public/index.html` add a link to the semantic-ui stylesheet from a CDN
75 |
76 | ```html
77 |
78 |
79 |
80 |
81 |
82 |
83 | ```
84 |
85 | And restart the app :
86 |
87 | ```sh
88 | npm start
89 | ```
90 |
91 | Nothing should have changed by now
92 |
93 | ## Replace placeholder UI with our own
94 |
95 | In `src/App.js` delete the entire file and replace it with :
96 |
97 | ```jsx
98 | // src/App.js
99 |
100 | import React from "react";
101 | import { Header } from "semantic-ui-react";
102 |
103 | const App = () => {
104 | return (
105 |
106 |
107 |
108 | );
109 | };
110 |
111 | export default App;
112 | ```
113 |
114 | As expected, the app now consists of a header that says Hello World.
115 |
116 | Let's commit our changes before continuing :
117 |
118 | ```sh
119 | git add --all
120 | git commit -m "integrate react-semantic-ui and add hello world example"
121 | ```
122 |
123 | # Initialize Amplify
124 |
125 | ```sh
126 | amplify init
127 |
128 | ? Enter a name for the project photoalbums
129 | ? Enter a name for the environment dev
130 | ? Choose your default editor: Visual Studio Code
131 | ? Choose the type of app that you're building javascript
132 | Please tell us about your project
133 | ? What javascript framework are you using react
134 | ? Source Directory Path: src
135 | ? Distribution Directory Path: build
136 | ? Build Command: npm run-script build
137 | ? Start Command: npm run-script start
138 | ? Do you want to use an AWS profile? Yes
139 | ? Please choose the profile you want to use defaultoraprofile
140 |
141 | ```
142 |
143 | This will create an amplify project locally and on the cloud that we will build on for the rest of our project.
144 |
145 | Before we continue let's commit our progress :
146 |
147 | ```sh
148 | git add --all
149 |
150 | git commit -m "added amplify setup to react app"
151 | ```
152 |
153 | # Add a GraphQL API with Amplify
154 |
155 | We now want to create an API for creating albums entities.
156 |
157 | These albums will only have a name at first.
158 |
159 | > To build our API we’ll use AWS AppSync, a managed GraphQL service for building data-driven apps.
160 | > If you’re not yet familiar with the basics of GraphQL, you should take a few minutes and check out https://graphql.github.io/learn/ before continuing,
161 | > or use the site to refer back to when you have questions as you read along.
162 |
163 | ## Add an AWS AppSync API
164 |
165 | ```sh
166 | amplify add api
167 |
168 | ? Please select from one of the below mentioned services: GraphQL
169 | ? Provide API name: photoalbums
170 | ? Choose the default authorization type for the API Amazon Cognito User Pool
171 | Using service: Cognito, provided by: awscloudformation
172 |
173 | The current configured provider is Amazon Cognito.
174 |
175 | ? Do you want to use the default authentication and security configuration? Default configuration
176 | Warning: you will not be able to edit these selections.
177 | ? How do you want users to be able to sign in? Username
178 | ? Do you want to configure advanced settings? No, I am done.
179 | Successfully added auth resource
180 | ? Do you want to configure advanced settings for the GraphQL API (Use arrow keys)
181 | ❯ No, I am done.
182 | Yes, I want to make some additional changes.
183 | ? Do you have an annotated GraphQL schema? (y/N) N
184 | ? Do you want a guided schema creation? Yes
185 | Single object with fields (e.g., “Todo” with ID, name, description)
186 | ❯ One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
187 | Objects with fine-grained access control (e.g., a project management app with
188 | owner-based authorization)
189 | ? Do you want to edit the schema now? (Y/n) Y
190 | ? Press enter to continue
191 | ```
192 |
193 | Enter the following schema :
194 |
195 | ```graphql
196 | type Album @model @auth(rules: [{ allow: owner }]) {
197 | id: ID!
198 | name: String!
199 | createdAt: String!
200 | photos: [Photo] @connection(name: "AlbumPhotos")
201 | }
202 |
203 | type Photo @model @auth(rules: [{ allow: owner }]) {
204 | id: ID!
205 | album: Album @connection(name: "AlbumPhotos")
206 | bucket: String!
207 | name: String!
208 | createdAt: String!
209 | }
210 | ```
211 |
212 | and press enter.
213 |
214 | Before we continue let's commit our changes
215 |
216 | ```sh
217 | git add --all
218 | git commit -m "initialized offline amplify api"
219 | ```
220 |
221 | And we can then push our changes to the cloud to have Amplify setup the API for us
222 |
223 | ```sh
224 | amplify push
225 |
226 | ? Are you sure you want to continue? Yes
227 | ? Do you want to generate code for your newly created GraphQL API (Y/n) Y
228 | ```
229 |
230 | In addition to the generated API, Amplify generated some code for us that we will examine later. But meanwhile let's commit our changes.
231 |
232 | ```sh
233 | git add --all
234 | git commit -m "added amplify codegen and deployed appsync api"
235 | ```
236 |
237 | ## Manage Albums UI
238 |
239 | Let’s update our front-end to:
240 |
241 | - allow users to create albums
242 | - show a list of albums
243 | - allow users to click into an album to view its details
244 |
245 | To do that we'll need multiple routes. We can use `react-router-dom` to create and manage routes.
246 |
247 | So let's install the dependencies
248 |
249 | ```sh
250 | npm i react-router-dom @aws-amplify/datastore @aws-amplify/core lodash
251 | ```
252 |
253 | First we setup the UI code without any interaction with the API:
254 |
255 | ```jsx
256 | import React from "react";
257 | import { Grid, Header, Input, List, Segment } from "semantic-ui-react";
258 | import { BrowserRouter as Router, Route, NavLink } from "react-router-dom";
259 | import sortBy from "lodash/sortBy";
260 |
261 | const NewAlbum = () => {
262 | const [albumName, setAlbumName] = React.useState("");
263 | const handleSubmit = () => {
264 | console.log(`Creating album ${albumName} `);
265 | setAlbumName("");
266 | };
267 | const handleChange = event => {
268 | setAlbumName(event.target.value);
269 | };
270 | return (
271 |
272 |
273 |
283 |
284 | );
285 | };
286 |
287 | const AlbumsList = ({ albums = [] }) => {
288 | return (
289 |
290 |
291 |
292 | {sortBy(albums, ["createdAt"]).map(album => (
293 |
294 | {album.name}
295 |
296 | ))}
297 |
298 |
299 | );
300 | };
301 | const AlbumDetailsLoader = ({ id }) => {
302 | const [isLoading] = React.useState(true);
303 | const [album] = React.useState({});
304 | if (isLoading) {
305 | return Loading...
;
306 | }
307 | return ;
308 | };
309 |
310 | const AlbumDetails = ({ album }) => {
311 | return (
312 |
313 |
314 | TODO: Allow photo uploads
315 | TODO: Show photos for this album
316 |
317 | );
318 | };
319 |
320 | const AlbumsListLoader = () => {
321 | const [isLoading] = React.useState(true);
322 | const [albums] = React.useState([]);
323 | if (isLoading) return null;
324 | return ;
325 | };
326 |
327 | const App = () => {
328 | return (
329 |
330 |
331 |
332 |
333 |
334 |
335 | (
338 |
339 | Back to Albums list
340 |
341 | )}
342 | />
343 | (
346 |
347 | )}
348 | />
349 |
350 |
351 |
352 | );
353 | };
354 |
355 | export default App;
356 | ```
357 |
358 | Running `npm start` now should show us the app without any logic attached to it.
359 |
360 | Then let's commit our changes to git
361 |
362 | ```sh
363 | git add --all
364 | git commit -m "added UI code without connection to API."
365 | ```
366 |
367 | Before we connect to the API we will need to allow users to authenticate.
368 |
369 | # Add Authentication
370 |
371 | Amplify makes it fast to add industrial strength authentication to our app.
372 |
373 | ## Setup the auth front-end
374 |
375 | Amplify provides a solid set of tools that make this step extremely straight-forward.
376 |
377 | We start by adding amplify front end dependencies :
378 |
379 | ```sh
380 | npm install --save aws-amplify aws-amplify-react
381 | ```
382 |
383 | And then we'll use the `withAuthenticator` higher-order React components to wrap our existing app component. This will take care of rendering a simple UI for letting users sign up, confirm their account, sign in, sign out, or reset their password.
384 |
385 | In `src/App.js` import `withAuthenticator`and instead of exporting App we export withAuthenticator(App)
386 |
387 | ```jsx
388 | // src/App.js
389 |
390 | // ...
391 | import { withAuthenticator } from "aws-amplify-react";
392 | import Amplify from "@aws-amplify/core";
393 | // This was added by amplify when we initialized it and added auth.
394 | import aws_exports from "./aws-exports";
395 | // We use the generated file to config Amplify with our desired settings
396 | Amplify.configure(aws_exports);
397 |
398 | // ...
399 |
400 | const App = () => {
401 | // ...
402 | // ...
403 | // ...
404 | };
405 |
406 | export default withAuthenticator(App, { includeGreetings: true });
407 | ```
408 |
409 | We can commit these small changes that adds authentication to our front-end UI
410 |
411 | ```sh
412 | git add --all
413 |
414 | git commit -m "add authentication ui"
415 | ```
416 |
417 | ## Recap : What we changed in App.js
418 |
419 | - Imported and configured the AWS Amplify JS library
420 |
421 | - Imported the withAuthenticator higher order component from aws-amplify-react
422 |
423 | - Wrapped the App component using withAuthenticator
424 |
425 | ## Try it out : Create an account
426 |
427 | Enter your information with a valid email (it will be used to send you a verification code).
428 |
429 | Then login with the account you've created and you will get back to a page containing the App component and a header with your username and a link to log you out.
430 |
431 | # Connect the app to the AppSync API
432 |
433 | ## Allow users to create albums
434 |
435 | We start by importing createAlbum mutation and :
436 |
437 | ```jsx
438 | import React from "react";
439 | import { Grid, Header, Input, List, Segment } from "semantic-ui-react";
440 | import { BrowserRouter as Router, Route, NavLink } from "react-router-dom";
441 | import sortBy from "lodash/sortBy";
442 | import { withAuthenticator } from "aws-amplify-react";
443 | import { createAlbum } from "./graphql/mutations";
444 | import API, { graphqlOperation } from "@aws-amplify/api";
445 | import Amplify from "aws-amplify";
446 | import awsconfig from "./aws-exports";
447 | Amplify.configure(awsconfig);
448 |
449 | const NewAlbum = () => {
450 | const [albumName, setAlbumName] = React.useState("");
451 | const handleSubmit = async () => {
452 | console.log(`Creating album ${albumName} `);
453 | await API.graphql(
454 | graphqlOperation(createAlbum, {
455 | input: {
456 | name: albumName,
457 | createdAt: `${Date.now()}`
458 | }
459 | })
460 | );
461 | setAlbumName("");
462 | };
463 | const handleChange = event => {
464 | setAlbumName(event.target.value);
465 | };
466 | return (
467 |
468 |
469 |
479 |
480 | );
481 | };
482 | ```
483 |
484 | ## Show a live list of albums
485 |
486 | In the previous section we've seen how to add new albums, the next logical step is to display a list of the added albums.
487 |
488 | To do that we'll update our AlbumsListLoader to fetch albums data and listen to new updates.
489 |
490 | ```jsx
491 | import Amplify, { Auth } from "aws-amplify";
492 |
493 | import { onCreateAlbum } from "./graphql/subscriptions";
494 | import { listAlbums } from "./graphql/queries";
495 |
496 | const AlbumsListLoader = () => {
497 | const [isLoading, setIsLoading] = React.useState(true);
498 | const [albums, setAlbums] = React.useState([]);
499 | React.useEffect(() => {
500 | setIsLoading(true);
501 | // Get initial albums list
502 | API.graphql(graphqlOperation(listAlbums)).then(albs => {
503 | setAlbums(albs.data.listAlbums.items);
504 | setIsLoading(false);
505 | });
506 |
507 | Auth.currentAuthenticatedUser().then(user => {
508 | // Listen to new albums being added
509 | API.graphql(
510 | graphqlOperation(onCreateAlbum, { owner: user.username })
511 | ).subscribe(newAlbum => {
512 | const albumRecord = newAlbum.value.data.onCreateAlbum;
513 | setAlbums(albs => [...albs, albumRecord]);
514 | });
515 | });
516 | }, []);
517 | if (isLoading) return null;
518 | return ;
519 | };
520 | ```
521 |
522 | ## Allow users to click into an album to view its details
523 |
524 | ```jsx
525 | import { listAlbums, getAlbum } from "./graphql/queries";
526 |
527 | const AlbumDetailsLoader = ({ id }) => {
528 | const [isLoading, setIsLoading] = React.useState(false);
529 | const [album, setAlbum] = React.useState({});
530 |
531 | React.useEffect(() => {
532 | setIsLoading(true);
533 | API.graphql(graphqlOperation(getAlbum, { id })).then(albumDetails => {
534 | setIsLoading(false);
535 | setAlbum(albumDetails.data.getAlbum);
536 | });
537 | }, [id]);
538 |
539 | if (isLoading) {
540 | return Loading...
;
541 | }
542 | return ;
543 | };
544 | ```
545 |
546 | By now we have a running app with a secure infinitely scalable API that syncs our data to DynamoDB on AWS .
547 |
548 | Let's do a quick commit :
549 |
550 | ```sh
551 | git add --all
552 | git commit -m "working app with AppSync"
553 | ```
554 |
555 | And now to add storage so we can store uploaded images in the cloud.
556 |
557 | ## Try out the app
558 |
559 | Check out the app now and try out the new features:
560 |
561 | View the list of albums
562 |
563 | Create a new album and see it appear in the albums list
564 |
565 | Click into an album to see the beginnings of our Album details view
566 |
567 | When viewing an Album, click ‘Back to Albums list’ to go home
568 |
569 | # Add Cloud Storage
570 |
571 | We’ll need a place to store all of the photos that get uploaded to our albums. Amazon Simple Storage Service (S3) is a great option for this and Amplify’s Storage module makes setting up and working with S3 very easy
572 |
573 | We'll start by adding the storage category to the amplify app.
574 |
575 | ```sh
576 | $ amplify add storage
577 |
578 |
579 | ? Please select from one of the below mentioned services:
580 |
581 | Content (Images, audio, video, etc.)
582 |
583 |
584 | ? Please provide a friendly name for your resource that will be used to label this category in the project: photoalbumsstorage
585 |
586 |
587 | ? Please provide bucket name:
588 |
589 |
590 | ? Who should have access: Auth and guest users
591 |
592 |
593 | ? What kind of access do you want for Authenticated users?
594 | ◉ create/update
595 | ◉ read
596 | ◉ delete
597 |
598 |
599 | ? What kind of access do you want for Guest users?
600 | ◯ create/update
601 | ◉ read
602 | ◯ delete
603 |
604 |
605 | ? Do you want to add a Lambda Trigger for your S3 Bucket? No
606 |
607 |
608 | ? Select from the following options
609 | Create a new function
610 |
611 |
612 | ? Do you want to edit the local S3Triggerxxxxxxx lambda function now? (Y/n)
613 | No
614 | ```
615 |
616 | And then we push our resources to the cloud as we usually do with :
617 |
618 | ```sh
619 | amplify push
620 | ```
621 |
622 | And then commit our changes :
623 |
624 | ```sh
625 | git add --all
626 | git commit -m "added storage category and sample lambda function"
627 | ```
628 |
629 | ## Manage photos
630 |
631 | To be able to upload photos we'll create a S3ImageUpload component.
632 |
633 | We'll need to bring in a small dependency to help us generate photo names :
634 |
635 | ```sh
636 | npm install --save uuid
637 | ```
638 |
639 | ```jsx
640 | // Rest of deps
641 | import { Storage } from "aws-amplify";
642 | import { S3Image } from "aws-amplify-react";
643 | import { v4 as uuid } from "uuid";
644 | import { Grid, Header, Input, List, Segment, Form } from "semantic-ui-react";
645 |
646 | const uploadFile = async (event, albumId) => {
647 | const {
648 | target: { value, files }
649 | } = event;
650 | const user = await Auth.currentAuthenticatedUser();
651 | const fileForUpload = files[0];
652 | const file = fileForUpload || value;
653 | const extension = file.name.split(".")[1];
654 | const { type: mimeType } = file;
655 | const key = `images/${uuid()}${albumId}.${extension}`;
656 | try {
657 | await Storage.put(key, file, {
658 | contentType: mimeType,
659 | metadata: {
660 | owner: user.username,
661 | albumId
662 | }
663 | });
664 | console.log("successfully uploaded image!");
665 | } catch (err) {
666 | console.log("error: ", err);
667 | }
668 | };
669 |
670 | const S3ImageUpload = ({ albumId }) => {
671 | const [isUploading, setIsUploading] = React.useState(false);
672 | const onChange = async event => {
673 | setIsUploading(true);
674 |
675 | let files = [];
676 | for (var i = 0; i < event.target.files.length; i++) {
677 | files.push(event.target.files.item(i));
678 | }
679 | await Promise.all(files.map(f => uploadFile(event, albumId)));
680 |
681 | setIsUploading(false);
682 | };
683 | return (
684 |
685 |
document.getElementById("add-image-file-input").click()}
687 | disabled={isUploading}
688 | icon="file image outline"
689 | content={isUploading ? "Uploading..." : "Add Images"}
690 | />
691 |
699 |
700 | );
701 | };
702 |
703 | const AlbumDetails = ({ album }) => {
704 | return (
705 |
706 |
707 |
708 | TODO: Show photos for this album
709 |
710 | );
711 | };
712 | ```
713 |
714 | By adding this code we should be able to upload an image and view it in the AWS bucket we specified when setting up the Storage category. Check your s3 bucket (https://s3.console.aws.amazon.com/s3/home) to make sure your file is being uploaded as expected.
715 |
716 | We still need to :
717 |
718 | - Save the picture information as part of the album
719 | - Retrieve and display pictures of an album
720 |
721 | Let's start by saving the picture information to our AppSync API.
722 |
723 | We will need to modify our uploadFile method :
724 |
725 | ```js
726 | import { createAlbum, createPhoto } from "./graphql/mutations";
727 |
728 | const uploadFile = async (event, albumId) => {
729 | const {
730 | target: { value, files }
731 | } = event;
732 | const fileForUpload = files[0];
733 | const file = fileForUpload || value;
734 | const extension = file.name.split(".")[1];
735 | const { type: mimeType } = file;
736 | const key = `images/${uuid()}${albumId}.${extension}`;
737 | try {
738 | await Storage.put(key, file, {
739 | contentType: mimeType
740 | });
741 | console.log("successfully uploaded image!");
742 | } catch (err) {
743 | console.log("error: ", err);
744 | }
745 | await API.graphql(
746 | graphqlOperation(createPhoto, {
747 | input: {
748 | bucket: awsconfig.aws_user_files_s3_bucket,
749 | name: key,
750 | createdAt: `${Date.now()}`,
751 | photoAlbumId: albumId
752 | }
753 | })
754 | );
755 | };
756 | ```
757 |
758 | And to retrieve and display pictures of an album:
759 |
760 | ```jsx
761 | import {
762 | Grid,
763 | Header,
764 | Input,
765 | List,
766 | Segment,
767 | Form,
768 | Divider
769 | } from "semantic-ui-react";
770 |
771 | const PhotosList = ({ photos }) => {
772 | return (
773 |
774 |
775 | {photos.map(photo => (
776 |
786 | ))}
787 |
788 | );
789 | };
790 |
791 | const AlbumDetails = ({ album }) => {
792 | return (
793 |
794 |
795 |
796 |
797 |
798 | );
799 | };
800 | ```
801 |
802 | By now we still need to refresh the page to see the uploaded image on the page.
803 |
804 | To directly have the photos be updated, we can either setup a subscription to new photo creation or to change events in the album.
805 |
806 | Let's listen to the onCreatePhoto event for a more granular update.
807 |
808 | But before we do, a quick commit :
809 |
810 | ```sh
811 | git add --all
812 | git commit -m "added s3 upload and display photo functionality"
813 | ```
814 |
815 | AlbumDetailsLoader becomes
816 |
817 | ```jsx
818 | const AlbumDetailsLoader = ({ id }) => {
819 | const [isLoading, setIsLoading] = React.useState(false);
820 | const [album, setAlbum] = React.useState({});
821 |
822 | React.useEffect(() => {
823 | setIsLoading(true);
824 | API.graphql(graphqlOperation(getAlbum, { id })).then(albumDetails => {
825 | setIsLoading(false);
826 | setAlbum(albumDetails.data.getAlbum);
827 | });
828 | Auth.currentAuthenticatedUser().then(user => {
829 | API.graphql(
830 | graphqlOperation(onCreatePhoto, { owner: user.username })
831 | ).subscribe(photo => {
832 | const newPhoto = photo.value.data.onCreatePhoto;
833 | setAlbum(alb => {
834 | return { ...alb, photos: { items: [newPhoto, ...alb.photos.items] } };
835 | });
836 | });
837 | });
838 | }, [id]);
839 |
840 | if (isLoading) {
841 | return Loading...
;
842 | }
843 | return ;
844 | };
845 | ```
846 |
847 | # Refactor : move auth to React Context
848 |
849 | Up till now we've been fetching the user's username in multiple components, in order to avoid having promises everywhere in our `useEffect`s let's store the user auth data in context.
850 |
851 | To do that we'll store authentication credentials at the top level of our app
852 |
853 | In our App component
854 |
855 | ```jsx
856 | const UserContext = React.createContext({ username: null });
857 |
858 | const App = () => {
859 | const [user, setUser] = React.useState({ username: null });
860 | React.useEffect(() => {
861 | Auth.currentAuthenticatedUser().then(user => {
862 | setUser(user);
863 | });
864 | }, []);
865 | return (
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 | (
876 |
877 | Back to Albums list
878 |
879 | )}
880 | />
881 | (
884 |
885 | )}
886 | />
887 |
888 |
889 |
890 |
891 | );
892 | };
893 | ```
894 |
895 | And we can then retrieve the username by using `useContext` for example AlbumsListLoader's effect becomes :
896 |
897 | ```jsx
898 | const AlbumsListLoader = () => {
899 | const [isLoading, setIsLoading] = React.useState(true);
900 | const [albums, setAlbums] = React.useState([]);
901 | const { username } = React.useContext(UserContext);
902 | React.useEffect(() => {
903 | let isMounted = true;
904 | if (!username) return;
905 | setIsLoading(true);
906 | API.graphql(graphqlOperation(listAlbums)).then(albs => {
907 | if (!isMounted) return;
908 | setAlbums(albs.data.listAlbums.items);
909 | setIsLoading(false);
910 | });
911 | const sub = API.graphql(
912 | graphqlOperation(onCreateAlbum, { owner: username })
913 | ).subscribe(newAlbum => {
914 | const albumRecord = newAlbum.value.data.onCreateAlbum;
915 | setAlbums(albs => [...albs, albumRecord]);
916 | });
917 | return () => {
918 | sub.unsubscribe();
919 | isMounted = false;
920 | };
921 | }, [username]);
922 | if (isLoading) return null;
923 | return ;
924 | };
925 | ```
926 |
927 | And we commit before moving to the last part:
928 |
929 | ```sh
930 | git add --all
931 | git commit -m "moved auth data to context and cleanup in effects"
932 | ```
933 |
934 | # Extract labels from images using AI
935 |
936 | In our graphQL schema `amplify/backend/api/photoalbums/schema.graphql` let's tell amplify that we're interested in turning images to text.
937 |
938 | ```graphql
939 | type Album @model @auth(rules: [{ allow: owner }]) {
940 | id: ID!
941 | name: String!
942 | createdAt: String!
943 | photos: [Photo] @connection(name: "AlbumPhotos")
944 | }
945 |
946 | type Photo @model @auth(rules: [{ allow: owner }]) {
947 | id: ID!
948 | album: Album @connection(name: "AlbumPhotos")
949 | bucket: String!
950 | name: String!
951 | createdAt: String!
952 | }
953 |
954 | type Query {
955 | convertImageToText: String @predictions(actions: [identifyLabels])
956 | }
957 | ```
958 |
959 | Then we ask amplify to create the needed resources on the cloud
960 |
961 | ```sh
962 | amplify push
963 | ```
964 |
965 | Now we can extract labels from uploaded images by simply running a graphQL query on our API.
966 |
967 | In code this looks like the following :
968 |
969 | ```jsx
970 | import { listAlbums, getAlbum, convertImageToText } from "./graphql/queries";
971 |
972 | const uploadFile = async (event, albumId, username) => {
973 | const {
974 | target: { value, files }
975 | } = event;
976 | const fileForUpload = files[0];
977 | const file = fileForUpload || value;
978 | const extension = file.name.split(".")[1];
979 | const { type: mimeType } = file;
980 | const key = `images/${uuid()}${albumId}.${extension}`;
981 | let s3Obj;
982 | try {
983 | s3Obj = await Storage.put(key, file, {
984 | contentType: mimeType,
985 | metadata: {
986 | owner: username,
987 | albumId
988 | }
989 | });
990 | console.log("successfully uploaded image!");
991 | } catch (err) {
992 | console.log("error: ", err);
993 | return;
994 | }
995 | const s3ImageKey = s3Obj.key;
996 | const predictionResult = await API.graphql(
997 | graphqlOperation(convertImageToText, {
998 | input: {
999 | identifyLabels: {
1000 | key: s3ImageKey
1001 | }
1002 | }
1003 | })
1004 | );
1005 | const imageLabels = predictionResult.data.convertImageToText;
1006 | console.warn({ imageLabels });
1007 |
1008 | await API.graphql(
1009 | graphqlOperation(createPhoto, {
1010 | input: {
1011 | bucket: awsconfig.aws_user_files_s3_bucket,
1012 | name: key,
1013 | createdAt: `${Date.now()}`,
1014 | photoAlbumId: albumId
1015 | }
1016 | })
1017 | );
1018 | };
1019 | ```
1020 |
1021 | If you try it, it should output an array of labels describing the image.
1022 |
1023 | ## Store labels in db
1024 |
1025 | As a last step let's add an array of strings to the graphql Photo type and display it in our UI.
1026 |
1027 | We start by modifying the Photo graphQL type
1028 |
1029 | ```graphql
1030 | type Photo @model @auth(rules: [{ allow: owner }]) {
1031 | id: ID!
1032 | album: Album @connection(name: "AlbumPhotos")
1033 | bucket: String!
1034 | name: String!
1035 | createdAt: String!
1036 | labels: [String]
1037 | }
1038 | ```
1039 |
1040 | and push
1041 |
1042 | ```sh
1043 | amplify push
1044 | ```
1045 |
1046 | And last we write the labels to our photo object after running the prediction in the `uploadFile` method
1047 |
1048 | ```js
1049 | const s3ImageKey = s3Obj.key;
1050 | const predictionResult = await API.graphql(
1051 | graphqlOperation(convertImageToText, {
1052 | input: {
1053 | identifyLabels: {
1054 | key: s3ImageKey
1055 | }
1056 | }
1057 | })
1058 | );
1059 | const imageLabels = predictionResult.data.convertImageToText;
1060 | console.warn({ imageLabels });
1061 |
1062 | await API.graphql(
1063 | graphqlOperation(createPhoto, {
1064 | input: {
1065 | bucket: awsconfig.aws_user_files_s3_bucket,
1066 | name: key,
1067 | createdAt: `${Date.now()}`,
1068 | photoAlbumId: albumId,
1069 | labels: imageLabels
1070 | }
1071 | })
1072 | );
1073 | ```
1074 |
1075 | And in our PhotosList component :
1076 |
1077 | ```jsx
1078 | const PhotosList = ({ photos }) => {
1079 | return (
1080 |
1081 |
1082 | {photos &&
1083 | photos.items &&
1084 | photos.items.map(photo => (
1085 |
1086 |
1096 | Label : {photo.labels && photo.labels.join(" ")}
1097 |
1098 | ))}
1099 |
1100 | );
1101 | };
1102 | ```
1103 |
1104 | And our final commit :
1105 |
1106 | ```sh
1107 | git add --all
1108 | git commit -m "added image labeling + labels storing and displaying"
1109 | ```
1110 |
1111 | # Deploy
1112 |
1113 | We've built a simple single page application that can be deployed to any static file server.
1114 |
1115 | To stick with AWS let's use Amplify to deploy it to a share-able URL, while keeping in mind that any provider would do.
1116 |
1117 | ```sh
1118 | amplify hosting add
1119 | ? Select the environment setup: DEV (S3 only with HTTP)
1120 | ? hosting bucket name photoalbums-20191221083147-hostingbucket
1121 | ? index doc for the website index.html
1122 | ? error doc for the website index.html
1123 |
1124 | amplify publish
1125 |
1126 | ```
1127 |
1128 | And this deploys our site to a unique development URL : http://photoalbums-20191221083147-hostingbucket-dev.s3-website-us-east-1.amazonaws.com/
1129 |
--------------------------------------------------------------------------------
/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "photoalbums",
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/photoalbums/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSyncApiName": "photoalbums",
3 | "DynamoDBBillingMode": "PAY_PER_REQUEST",
4 | "DynamoDBEnableServerSideEncryption": "false",
5 | "AuthCognitoUserPoolId": {
6 | "Fn::GetAtt": [
7 | "authphotoalbums9240b8c0",
8 | "Outputs.UserPoolId"
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/amplify/backend/api/photoalbums/schema.graphql:
--------------------------------------------------------------------------------
1 | type Album @model @auth(rules: [{ allow: owner }]) {
2 | id: ID!
3 | name: String!
4 | createdAt: String!
5 | photos: [Photo] @connection(name: "AlbumPhotos")
6 | }
7 |
8 | type Photo @model @auth(rules: [{ allow: owner }]) {
9 | id: ID!
10 | album: Album @connection(name: "AlbumPhotos")
11 | bucket: String!
12 | name: String!
13 | createdAt: String!
14 | labels: [String]
15 | }
16 |
17 | type Query {
18 | convertImageToText: String @predictions(actions: [identifyLabels])
19 | }
20 |
--------------------------------------------------------------------------------
/amplify/backend/api/photoalbums/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/api/photoalbums/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 5
3 | }
--------------------------------------------------------------------------------
/amplify/backend/auth/photoalbums9240b8c0/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "identityPoolName": "photoalbums9240b8c0_identitypool_9240b8c0",
3 | "allowUnauthenticatedIdentities": true,
4 | "resourceNameTruncated": "photoa9240b8c0",
5 | "userPoolName": "photoalbums9240b8c0_userpool_9240b8c0",
6 | "autoVerifiedAttributes": [
7 | "email"
8 | ],
9 | "mfaConfiguration": "OFF",
10 | "mfaTypes": [
11 | "SMS Text Message"
12 | ],
13 | "smsAuthenticationMessage": "Your authentication code is {####}",
14 | "smsVerificationMessage": "Your verification code is {####}",
15 | "emailVerificationSubject": "Your verification code",
16 | "emailVerificationMessage": "Your verification code is {####}",
17 | "defaultPasswordPolicy": false,
18 | "passwordPolicyMinLength": 8,
19 | "passwordPolicyCharacters": [],
20 | "requiredAttributes": [
21 | "email"
22 | ],
23 | "userpoolClientGenerateSecret": true,
24 | "userpoolClientRefreshTokenValidity": 30,
25 | "userpoolClientWriteAttributes": [
26 | "email"
27 | ],
28 | "userpoolClientReadAttributes": [
29 | "email"
30 | ],
31 | "userpoolClientLambdaRole": "photoa9240b8c0_userpoolclient_lambda_role",
32 | "userpoolClientSetAttributes": false,
33 | "resourceName": "photoalbums9240b8c0",
34 | "authSelections": "identityPoolAndUserPool",
35 | "authRoleArn": {
36 | "Fn::GetAtt": [
37 | "AuthRole",
38 | "Arn"
39 | ]
40 | },
41 | "unauthRoleArn": {
42 | "Fn::GetAtt": [
43 | "UnauthRole",
44 | "Arn"
45 | ]
46 | },
47 | "useDefault": "default",
48 | "userPoolGroupList": [],
49 | "dependsOn": []
50 | }
--------------------------------------------------------------------------------
/amplify/backend/auth/photoalbums9240b8c0/photoalbums9240b8c0-cloudformation-template.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 |
3 | Parameters:
4 | env:
5 | Type: String
6 | authRoleArn:
7 | Type: String
8 | unauthRoleArn:
9 | Type: String
10 |
11 |
12 |
13 |
14 | identityPoolName:
15 | Type: String
16 |
17 | allowUnauthenticatedIdentities:
18 | Type: String
19 |
20 | resourceNameTruncated:
21 | Type: String
22 |
23 | userPoolName:
24 | Type: String
25 |
26 | autoVerifiedAttributes:
27 | Type: CommaDelimitedList
28 |
29 | mfaConfiguration:
30 | Type: String
31 |
32 | mfaTypes:
33 | Type: CommaDelimitedList
34 |
35 | smsAuthenticationMessage:
36 | Type: String
37 |
38 | smsVerificationMessage:
39 | Type: String
40 |
41 | emailVerificationSubject:
42 | Type: String
43 |
44 | emailVerificationMessage:
45 | Type: String
46 |
47 | defaultPasswordPolicy:
48 | Type: String
49 |
50 | passwordPolicyMinLength:
51 | Type: Number
52 |
53 | passwordPolicyCharacters:
54 | Type: CommaDelimitedList
55 |
56 | requiredAttributes:
57 | Type: CommaDelimitedList
58 |
59 | userpoolClientGenerateSecret:
60 | Type: String
61 |
62 | userpoolClientRefreshTokenValidity:
63 | Type: Number
64 |
65 | userpoolClientWriteAttributes:
66 | Type: CommaDelimitedList
67 |
68 | userpoolClientReadAttributes:
69 | Type: CommaDelimitedList
70 |
71 | userpoolClientLambdaRole:
72 | Type: String
73 |
74 | userpoolClientSetAttributes:
75 | Type: String
76 |
77 | resourceName:
78 | Type: String
79 |
80 | authSelections:
81 | Type: String
82 |
83 | useDefault:
84 | Type: String
85 |
86 | userPoolGroupList:
87 | Type: CommaDelimitedList
88 |
89 | dependsOn:
90 | Type: CommaDelimitedList
91 |
92 | Conditions:
93 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ]
94 |
95 | Resources:
96 |
97 |
98 | # BEGIN SNS ROLE RESOURCE
99 | SNSRole:
100 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process
101 | Type: AWS::IAM::Role
102 | Properties:
103 | RoleName: !If [ShouldNotCreateEnvResources, 'photoa9240b8c0_sns-role', !Join ['',[ 'sns', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]]
104 | AssumeRolePolicyDocument:
105 | Version: "2012-10-17"
106 | Statement:
107 | - Sid: ""
108 | Effect: "Allow"
109 | Principal:
110 | Service: "cognito-idp.amazonaws.com"
111 | Action:
112 | - "sts:AssumeRole"
113 | Condition:
114 | StringEquals:
115 | sts:ExternalId: photoa9240b8c0_role_external_id
116 | Policies:
117 | -
118 | PolicyName: photoa9240b8c0-sns-policy
119 | PolicyDocument:
120 | Version: "2012-10-17"
121 | Statement:
122 | -
123 | Effect: "Allow"
124 | Action:
125 | - "sns:Publish"
126 | Resource: "*"
127 | # BEGIN USER POOL RESOURCES
128 | UserPool:
129 | # Created upon user selection
130 | # Depends on SNS Role for Arn if MFA is enabled
131 | Type: AWS::Cognito::UserPool
132 | UpdateReplacePolicy: Retain
133 | Properties:
134 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]]
135 |
136 | Schema:
137 |
138 | -
139 | Name: email
140 | Required: true
141 | Mutable: true
142 |
143 |
144 |
145 |
146 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes
147 |
148 |
149 | EmailVerificationMessage: !Ref emailVerificationMessage
150 | EmailVerificationSubject: !Ref emailVerificationSubject
151 |
152 | Policies:
153 | PasswordPolicy:
154 | MinimumLength: !Ref passwordPolicyMinLength
155 | RequireLowercase: false
156 | RequireNumbers: false
157 | RequireSymbols: false
158 | RequireUppercase: false
159 |
160 | MfaConfiguration: !Ref mfaConfiguration
161 | SmsVerificationMessage: !Ref smsVerificationMessage
162 | SmsConfiguration:
163 | SnsCallerArn: !GetAtt SNSRole.Arn
164 | ExternalId: photoa9240b8c0_role_external_id
165 |
166 |
167 | UserPoolClientWeb:
168 | # Created provide application access to user pool
169 | # Depends on UserPool for ID reference
170 | Type: "AWS::Cognito::UserPoolClient"
171 | Properties:
172 | ClientName: photoa9240b8c0_app_clientWeb
173 |
174 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
175 | UserPoolId: !Ref UserPool
176 | DependsOn: UserPool
177 | UserPoolClient:
178 | # Created provide application access to user pool
179 | # Depends on UserPool for ID reference
180 | Type: "AWS::Cognito::UserPoolClient"
181 | Properties:
182 | ClientName: photoa9240b8c0_app_client
183 |
184 | GenerateSecret: !Ref userpoolClientGenerateSecret
185 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
186 | UserPoolId: !Ref UserPool
187 | DependsOn: UserPool
188 | # BEGIN USER POOL LAMBDA RESOURCES
189 | UserPoolClientRole:
190 | # Created to execute Lambda which gets userpool app client config values
191 | Type: 'AWS::IAM::Role'
192 | Properties:
193 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',['upClientLambdaRole', !Select [3, !Split ['-', !Ref 'AWS::StackName']], '-', !Ref env]]]
194 | AssumeRolePolicyDocument:
195 | Version: '2012-10-17'
196 | Statement:
197 | - Effect: Allow
198 | Principal:
199 | Service:
200 | - lambda.amazonaws.com
201 | Action:
202 | - 'sts:AssumeRole'
203 | DependsOn: UserPoolClient
204 | UserPoolClientLambda:
205 | # Lambda which gets userpool app client config values
206 | # Depends on UserPool for id
207 | # Depends on UserPoolClientRole for role ARN
208 | Type: 'AWS::Lambda::Function'
209 | Properties:
210 | Code:
211 | ZipFile: !Join
212 | - |+
213 | - - 'const response = require(''cfn-response'');'
214 | - 'const aws = require(''aws-sdk'');'
215 | - 'const identity = new aws.CognitoIdentityServiceProvider();'
216 | - 'exports.handler = (event, context, callback) => {'
217 | - ' if (event.RequestType == ''Delete'') { '
218 | - ' response.send(event, context, response.SUCCESS, {})'
219 | - ' }'
220 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {'
221 | - ' const params = {'
222 | - ' ClientId: event.ResourceProperties.clientId,'
223 | - ' UserPoolId: event.ResourceProperties.userpoolId'
224 | - ' };'
225 | - ' identity.describeUserPoolClient(params).promise()'
226 | - ' .then((res) => {'
227 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});'
228 | - ' })'
229 | - ' .catch((err) => {'
230 | - ' response.send(event, context, response.FAILED, {err});'
231 | - ' });'
232 | - ' }'
233 | - '};'
234 | Handler: index.handler
235 | Runtime: nodejs8.10
236 | Timeout: '300'
237 | Role: !GetAtt
238 | - UserPoolClientRole
239 | - Arn
240 | DependsOn: UserPoolClientRole
241 | UserPoolClientLambdaPolicy:
242 | # Sets userpool policy for the role that executes the Userpool Client Lambda
243 | # Depends on UserPool for Arn
244 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing
245 | Type: 'AWS::IAM::Policy'
246 | Properties:
247 | PolicyName: photoa9240b8c0_userpoolclient_lambda_iam_policy
248 | Roles:
249 | - !Ref UserPoolClientRole
250 | PolicyDocument:
251 | Version: '2012-10-17'
252 | Statement:
253 | - Effect: Allow
254 | Action:
255 | - 'cognito-idp:DescribeUserPoolClient'
256 | Resource: !GetAtt UserPool.Arn
257 | DependsOn: UserPoolClientLambda
258 | UserPoolClientLogPolicy:
259 | # Sets log policy for the role that executes the Userpool Client Lambda
260 | # Depends on UserPool for Arn
261 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
262 | Type: 'AWS::IAM::Policy'
263 | Properties:
264 | PolicyName: photoa9240b8c0_userpoolclient_lambda_log_policy
265 | Roles:
266 | - !Ref UserPoolClientRole
267 | PolicyDocument:
268 | Version: 2012-10-17
269 | Statement:
270 | - Effect: Allow
271 | Action:
272 | - 'logs:CreateLogGroup'
273 | - 'logs:CreateLogStream'
274 | - 'logs:PutLogEvents'
275 | Resource: !Sub
276 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*
277 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda}
278 | DependsOn: UserPoolClientLambdaPolicy
279 | UserPoolClientInputs:
280 | # Values passed to Userpool client Lambda
281 | # Depends on UserPool for Id
282 | # Depends on UserPoolClient for Id
283 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
284 | Type: 'Custom::LambdaCallout'
285 | Properties:
286 | ServiceToken: !GetAtt UserPoolClientLambda.Arn
287 | clientId: !Ref UserPoolClient
288 | userpoolId: !Ref UserPool
289 | DependsOn: UserPoolClientLogPolicy
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 | # BEGIN IDENTITY POOL RESOURCES
298 |
299 |
300 | IdentityPool:
301 | # Always created
302 | Type: AWS::Cognito::IdentityPool
303 | Properties:
304 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'photoalbums9240b8c0_identitypool_9240b8c0', !Join ['',['photoalbums9240b8c0_identitypool_9240b8c0', '__', !Ref env]]]
305 |
306 | CognitoIdentityProviders:
307 | - ClientId: !Ref UserPoolClient
308 | ProviderName: !Sub
309 | - cognito-idp.${region}.amazonaws.com/${client}
310 | - { region: !Ref "AWS::Region", client: !Ref UserPool}
311 | - ClientId: !Ref UserPoolClientWeb
312 | ProviderName: !Sub
313 | - cognito-idp.${region}.amazonaws.com/${client}
314 | - { region: !Ref "AWS::Region", client: !Ref UserPool}
315 |
316 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities
317 |
318 |
319 | DependsOn: UserPoolClientInputs
320 |
321 |
322 | IdentityPoolRoleMap:
323 | # Created to map Auth and Unauth roles to the identity pool
324 | # Depends on Identity Pool for ID ref
325 | Type: AWS::Cognito::IdentityPoolRoleAttachment
326 | Properties:
327 | IdentityPoolId: !Ref IdentityPool
328 | Roles:
329 | unauthenticated: !Ref unauthRoleArn
330 | authenticated: !Ref authRoleArn
331 | DependsOn: IdentityPool
332 |
333 |
334 | Outputs :
335 |
336 | IdentityPoolId:
337 | Value: !Ref 'IdentityPool'
338 | Description: Id for the identity pool
339 | IdentityPoolName:
340 | Value: !GetAtt IdentityPool.Name
341 |
342 |
343 |
344 |
345 | UserPoolId:
346 | Value: !Ref 'UserPool'
347 | Description: Id for the user pool
348 | UserPoolName:
349 | Value: !Ref userPoolName
350 | AppClientIDWeb:
351 | Value: !Ref 'UserPoolClientWeb'
352 | Description: The user pool app client id for web
353 | AppClientID:
354 | Value: !Ref 'UserPoolClient'
355 | Description: The user pool app client id
356 | AppClientSecret:
357 | Value: !GetAtt UserPoolClientInputs.appSecret
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
--------------------------------------------------------------------------------
/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "auth": {
3 | "photoalbums9240b8c0": {
4 | "service": "Cognito",
5 | "providerPlugin": "awscloudformation",
6 | "dependsOn": []
7 | }
8 | },
9 | "api": {
10 | "photoalbums": {
11 | "service": "AppSync",
12 | "providerPlugin": "awscloudformation",
13 | "output": {
14 | "authConfig": {
15 | "additionalAuthenticationProviders": [],
16 | "defaultAuthentication": {
17 | "authenticationType": "AMAZON_COGNITO_USER_POOLS",
18 | "userPoolConfig": {
19 | "userPoolId": "authphotoalbums9240b8c0"
20 | }
21 | }
22 | }
23 | }
24 | }
25 | },
26 | "function": {
27 | "S3Triggerb01fd26e": {
28 | "service": "Lambda",
29 | "providerPlugin": "awscloudformation",
30 | "build": true
31 | }
32 | },
33 | "storage": {
34 | "photoalbumsstorage": {
35 | "service": "S3",
36 | "providerPlugin": "awscloudformation",
37 | "dependsOn": [
38 | {
39 | "category": "function",
40 | "resourceName": "S3Triggerb01fd26e",
41 | "attributes": [
42 | "Name",
43 | "Arn",
44 | "LambdaExecutionRole"
45 | ]
46 | }
47 | ]
48 | }
49 | },
50 | "hosting": {
51 | "S3AndCloudFront": {
52 | "service": "S3AndCloudFront",
53 | "providerPlugin": "awscloudformation"
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/amplify/backend/function/S3Triggerb01fd26e/S3Triggerb01fd26e-cloudformation-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Lambda resource stack creation using Amplify CLI",
4 | "Parameters": {
5 | "env": {
6 | "Type": "String"
7 | }
8 | },
9 | "Conditions": {
10 | "ShouldNotCreateEnvResources": {
11 | "Fn::Equals": [
12 | {
13 | "Ref": "env"
14 | },
15 | "NONE"
16 | ]
17 | }
18 | },
19 | "Resources": {
20 | "LambdaFunction": {
21 | "Type": "AWS::Lambda::Function",
22 | "Metadata": {
23 | "aws:asset:path": "./src",
24 | "aws:asset:property": "Code"
25 | },
26 | "Properties": {
27 | "Handler": "index.handler",
28 | "FunctionName": {
29 | "Fn::If": [
30 | "ShouldNotCreateEnvResources",
31 | "S3Triggerb01fd26e",
32 | {
33 | "Fn::Join": [
34 | "",
35 | [
36 | "S3Triggerb01fd26e",
37 | "-",
38 | {
39 | "Ref": "env"
40 | }
41 | ]
42 | ]
43 | }
44 | ]
45 | },
46 | "Environment": {
47 | "Variables": {
48 | "ENV": {
49 | "Ref": "env"
50 | }
51 | }
52 | },
53 | "Role": {
54 | "Fn::GetAtt": [
55 | "LambdaExecutionRole",
56 | "Arn"
57 | ]
58 | },
59 | "Runtime": "nodejs8.10",
60 | "Timeout": "25",
61 | "Code": {
62 | "S3Bucket": "amplify-photoalbums-dev-193536-deployment",
63 | "S3Key": "amplify-builds/S3Triggerb01fd26e-36706563445347654d67-build.zip"
64 | }
65 | }
66 | },
67 | "LambdaExecutionRole": {
68 | "Type": "AWS::IAM::Role",
69 | "Properties": {
70 | "RoleName": {
71 | "Fn::If": [
72 | "ShouldNotCreateEnvResources",
73 | "S3Triggerb01fd26eLambdaRoleb01fd26e",
74 | {
75 | "Fn::Join": [
76 | "",
77 | [
78 | "S3Triggerb01fd26eLambdaRoleb01fd26e",
79 | "-",
80 | {
81 | "Ref": "env"
82 | }
83 | ]
84 | ]
85 | }
86 | ]
87 | },
88 | "AssumeRolePolicyDocument": {
89 | "Version": "2012-10-17",
90 | "Statement": [
91 | {
92 | "Effect": "Allow",
93 | "Principal": {
94 | "Service": [
95 | "lambda.amazonaws.com"
96 | ]
97 | },
98 | "Action": [
99 | "sts:AssumeRole"
100 | ]
101 | }
102 | ]
103 | }
104 | }
105 | },
106 | "lambdaexecutionpolicy": {
107 | "DependsOn": [
108 | "LambdaExecutionRole"
109 | ],
110 | "Type": "AWS::IAM::Policy",
111 | "Properties": {
112 | "PolicyName": "lambda-execution-policy",
113 | "Roles": [
114 | {
115 | "Ref": "LambdaExecutionRole"
116 | }
117 | ],
118 | "PolicyDocument": {
119 | "Version": "2012-10-17",
120 | "Statement": [
121 | {
122 | "Effect": "Allow",
123 | "Action": [
124 | "logs:CreateLogGroup",
125 | "logs:CreateLogStream",
126 | "logs:PutLogEvents"
127 | ],
128 | "Resource": {
129 | "Fn::Sub": [
130 | "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
131 | {
132 | "region": {
133 | "Ref": "AWS::Region"
134 | },
135 | "account": {
136 | "Ref": "AWS::AccountId"
137 | },
138 | "lambda": {
139 | "Ref": "LambdaFunction"
140 | }
141 | }
142 | ]
143 | }
144 | }
145 | ]
146 | }
147 | }
148 | }
149 | },
150 | "Outputs": {
151 | "Name": {
152 | "Value": {
153 | "Ref": "LambdaFunction"
154 | }
155 | },
156 | "Arn": {
157 | "Value": {
158 | "Fn::GetAtt": [
159 | "LambdaFunction",
160 | "Arn"
161 | ]
162 | }
163 | },
164 | "Region": {
165 | "Value": {
166 | "Ref": "AWS::Region"
167 | }
168 | },
169 | "LambdaExecutionRole": {
170 | "Value": {
171 | "Ref": "LambdaExecutionRole"
172 | }
173 | }
174 | }
175 | }
--------------------------------------------------------------------------------
/amplify/backend/function/S3Triggerb01fd26e/src/event.json:
--------------------------------------------------------------------------------
1 | {
2 | "key1": "value1",
3 | "key2": "value2",
4 | "key3": "value3"
5 | }
--------------------------------------------------------------------------------
/amplify/backend/function/S3Triggerb01fd26e/src/index.js:
--------------------------------------------------------------------------------
1 | exports.handler = function(event, context) {
2 | //eslint-disable-line
3 | console.log('Received S3 event:', JSON.stringify(event, null, 2));
4 | // Get the object from the event and show its content type
5 | const bucket = event.Records[0].s3.bucket.name; //eslint-disable-line
6 | const key = event.Records[0].s3.object.key; //eslint-disable-line
7 | console.log(`Bucket: ${bucket}`, `Key: ${key}`);
8 | context.done(null, 'Successfully processed S3 event'); // SUCCESS with message
9 | };
10 |
--------------------------------------------------------------------------------
/amplify/backend/function/S3Triggerb01fd26e/src/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "S3Triggerb01fd26e",
3 | "version": "2.0.0",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/amplify/backend/function/S3Triggerb01fd26e/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "S3Triggerb01fd26e",
3 | "version": "2.0.0",
4 | "description": "Lambda function generated by Amplify",
5 | "main": "index.js",
6 | "license": "Apache-2.0"
7 | }
8 |
--------------------------------------------------------------------------------
/amplify/backend/hosting/S3AndCloudFront/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "bucketName": "photoalbums-20191221083147-hostingbucket"
3 | }
--------------------------------------------------------------------------------
/amplify/backend/hosting/S3AndCloudFront/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Hosting resource stack creation using Amplify CLI",
4 | "Parameters": {
5 | "env": {
6 | "Type": "String"
7 | },
8 | "bucketName": {
9 | "Type": "String"
10 | }
11 | },
12 | "Conditions": {
13 | "ShouldNotCreateEnvResources": {
14 | "Fn::Equals": [
15 | {
16 | "Ref": "env"
17 | },
18 | "NONE"
19 | ]
20 | }
21 | },
22 | "Resources": {
23 | "S3Bucket": {
24 | "Type": "AWS::S3::Bucket",
25 | "DeletionPolicy": "Retain",
26 | "Properties": {
27 | "BucketName": {
28 | "Fn::If": [
29 | "ShouldNotCreateEnvResources",
30 | {
31 | "Ref": "bucketName"
32 | },
33 | {
34 | "Fn::Join": [
35 | "",
36 | [
37 | {
38 | "Ref": "bucketName"
39 | },
40 | "-",
41 | {
42 | "Ref": "env"
43 | }
44 | ]
45 | ]
46 | }
47 | ]
48 | },
49 | "AccessControl": "Private",
50 | "WebsiteConfiguration": {
51 | "IndexDocument": "index.html",
52 | "ErrorDocument": "index.html"
53 | },
54 | "CorsConfiguration": {
55 | "CorsRules": [
56 | {
57 | "AllowedHeaders": [
58 | "Authorization",
59 | "Content-Length"
60 | ],
61 | "AllowedMethods": [
62 | "GET"
63 | ],
64 | "AllowedOrigins": [
65 | "*"
66 | ],
67 | "MaxAge": 3000
68 | }
69 | ]
70 | }
71 | }
72 | }
73 | },
74 | "Outputs": {
75 | "Region": {
76 | "Value": {
77 | "Ref": "AWS::Region"
78 | }
79 | },
80 | "HostingBucketName": {
81 | "Description": "Hosting bucket name",
82 | "Value": {
83 | "Ref": "S3Bucket"
84 | }
85 | },
86 | "WebsiteURL": {
87 | "Value": {
88 | "Fn::GetAtt": [
89 | "S3Bucket",
90 | "WebsiteURL"
91 | ]
92 | },
93 | "Description": "URL for website hosted on S3"
94 | },
95 | "S3BucketSecureURL": {
96 | "Value": {
97 | "Fn::Join": [
98 | "",
99 | [
100 | "https://",
101 | {
102 | "Fn::GetAtt": [
103 | "S3Bucket",
104 | "DomainName"
105 | ]
106 | }
107 | ]
108 | ]
109 | },
110 | "Description": "Name of S3 bucket to hold website content"
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/amplify/backend/storage/photoalbumsstorage/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "bucketName": "photoalbumsc08070c6ed664516be44ae077bcff5b8",
3 | "authPolicyName": "s3_amplify_fc6063de",
4 | "unauthPolicyName": "s3_amplify_fc6063de",
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_9e41d198",
23 | "s3PermissionsAuthenticatedUploads": "s3:PutObject",
24 | "s3UploadsPolicy": "Uploads_policy_9e41d198",
25 | "s3PermissionsAuthenticatedProtected": "s3:PutObject,s3:GetObject,s3:DeleteObject",
26 | "s3ProtectedPolicy": "Protected_policy_51364eb4",
27 | "s3PermissionsAuthenticatedPrivate": "s3:PutObject,s3:GetObject,s3:DeleteObject",
28 | "s3PrivatePolicy": "Private_policy_51364eb4",
29 | "AuthenticatedAllowList": "ALLOW",
30 | "s3ReadPolicy": "read_policy_9e41d198",
31 | "s3PermissionsGuestPublic": "s3:GetObject",
32 | "s3PermissionsGuestUploads": "DISALLOW",
33 | "GuestAllowList": "ALLOW",
34 | "triggerFunction": "S3Triggerb01fd26e"
35 | }
--------------------------------------------------------------------------------
/amplify/backend/storage/photoalbumsstorage/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 | "Default" : "NONE"
23 | },
24 | "s3PrivatePolicy": {
25 | "Type": "String",
26 | "Default" : "NONE"
27 | },
28 | "s3ProtectedPolicy": {
29 | "Type": "String",
30 | "Default" : "NONE"
31 | },
32 | "s3UploadsPolicy": {
33 | "Type": "String",
34 | "Default" : "NONE"
35 | },
36 | "s3ReadPolicy": {
37 | "Type": "String",
38 | "Default" : "NONE"
39 | },
40 | "s3PermissionsAuthenticatedPublic": {
41 | "Type": "String",
42 | "Default" : "DISALLOW"
43 | },
44 | "s3PermissionsAuthenticatedProtected": {
45 | "Type": "String",
46 | "Default" : "DISALLOW"
47 | },
48 | "s3PermissionsAuthenticatedPrivate": {
49 | "Type": "String",
50 | "Default" : "DISALLOW"
51 | },
52 | "s3PermissionsAuthenticatedUploads": {
53 | "Type": "String",
54 | "Default" : "DISALLOW"
55 | },
56 | "s3PermissionsGuestPublic": {
57 | "Type": "String",
58 | "Default" : "DISALLOW"
59 | },
60 | "s3PermissionsGuestUploads": {
61 | "Type": "String",
62 | "Default" : "DISALLOW" },
63 | "AuthenticatedAllowList": {
64 | "Type": "String",
65 | "Default" : "DISALLOW"
66 | },
67 | "GuestAllowList": {
68 | "Type": "String",
69 | "Default" : "DISALLOW"
70 | },
71 | "selectedGuestPermissions": {
72 | "Type": "CommaDelimitedList",
73 | "Default" : "NONE"
74 | },
75 | "selectedAuthenticatedPermissions": {
76 | "Type": "CommaDelimitedList",
77 | "Default" : "NONE"
78 | },
79 | "env": {
80 | "Type": "String"
81 | },
82 | "triggerFunction": {
83 | "Type": "String"
84 | },
85 |
86 |
87 |
88 | "functionS3Triggerb01fd26eName": {
89 | "Type": "String",
90 | "Default": "functionS3Triggerb01fd26eName"
91 | },
92 |
93 | "functionS3Triggerb01fd26eArn": {
94 | "Type": "String",
95 | "Default": "functionS3Triggerb01fd26eArn"
96 | },
97 |
98 | "functionS3Triggerb01fd26eLambdaExecutionRole": {
99 | "Type": "String",
100 | "Default": "functionS3Triggerb01fd26eLambdaExecutionRole"
101 | }
102 |
103 |
104 |
105 |
106 | },
107 | "Conditions": {
108 | "ShouldNotCreateEnvResources": {
109 | "Fn::Equals": [
110 | {
111 | "Ref": "env"
112 | },
113 | "NONE"
114 | ]
115 | },
116 | "CreateAuthPublic": {
117 | "Fn::Not" : [{
118 | "Fn::Equals" : [
119 | {"Ref" : "s3PermissionsAuthenticatedPublic"},
120 | "DISALLOW"
121 | ]
122 | }]
123 | },
124 | "CreateAuthProtected": {
125 | "Fn::Not" : [{
126 | "Fn::Equals" : [
127 | {"Ref" : "s3PermissionsAuthenticatedProtected"},
128 | "DISALLOW"
129 | ]
130 | }]
131 | },
132 | "CreateAuthPrivate": {
133 | "Fn::Not" : [{
134 | "Fn::Equals" : [
135 | {"Ref" : "s3PermissionsAuthenticatedPrivate"},
136 | "DISALLOW"
137 | ]
138 | }]
139 | },
140 | "CreateAuthUploads": {
141 | "Fn::Not" : [{
142 | "Fn::Equals" : [
143 | {"Ref" : "s3PermissionsAuthenticatedUploads"},
144 | "DISALLOW"
145 | ]
146 | }]
147 | },
148 | "CreateGuestPublic": {
149 | "Fn::Not" : [{
150 | "Fn::Equals" : [
151 | {"Ref" : "s3PermissionsGuestPublic"},
152 | "DISALLOW"
153 | ]
154 | }]
155 | },
156 | "CreateGuestUploads": {
157 | "Fn::Not" : [{
158 | "Fn::Equals" : [
159 | {"Ref" : "s3PermissionsGuestUploads"},
160 | "DISALLOW"
161 | ]
162 | }]
163 | },
164 | "AuthReadAndList": {
165 | "Fn::Not" : [{
166 | "Fn::Equals" : [
167 | {"Ref" : "AuthenticatedAllowList"},
168 | "DISALLOW"
169 | ]
170 | }]
171 | },
172 | "GuestReadAndList": {
173 | "Fn::Not" : [{
174 | "Fn::Equals" : [
175 | {"Ref" : "GuestAllowList"},
176 | "DISALLOW"
177 | ]
178 | }]
179 | }
180 | },
181 | "Resources": {
182 | "S3Bucket": {
183 | "Type": "AWS::S3::Bucket",
184 |
185 | "DependsOn": [
186 | "TriggerPermissions"
187 | ],
188 |
189 | "DeletionPolicy" : "Retain",
190 | "Properties": {
191 | "BucketName": {
192 | "Fn::If": [
193 | "ShouldNotCreateEnvResources",
194 | {
195 | "Ref": "bucketName"
196 | },
197 | {
198 | "Fn::Join": [
199 | "",
200 | [
201 | {
202 | "Ref": "bucketName"
203 | },
204 | {
205 | "Fn::Select": [
206 | 3,
207 | {
208 | "Fn::Split": [
209 | "-",
210 | {
211 | "Ref": "AWS::StackName"
212 | }
213 | ]
214 | }
215 | ]
216 | },
217 | "-",
218 | {
219 | "Ref": "env"
220 | }
221 | ]
222 | ]
223 | }
224 | ]
225 | },
226 |
227 | "NotificationConfiguration": {
228 | "LambdaConfigurations": [
229 | {
230 | "Event": "s3:ObjectCreated:*",
231 | "Function": {
232 | "Ref": "functionS3Triggerb01fd26eArn"
233 | }
234 | },
235 | {
236 | "Event": "s3:ObjectRemoved:*",
237 | "Function": {
238 | "Ref": "functionS3Triggerb01fd26eArn"
239 | }
240 | }
241 | ]
242 | },
243 |
244 | "CorsConfiguration": {
245 | "CorsRules": [
246 | {
247 | "AllowedHeaders": [
248 | "*"
249 | ],
250 | "AllowedMethods": [
251 | "GET",
252 | "HEAD",
253 | "PUT",
254 | "POST",
255 | "DELETE"
256 | ],
257 | "AllowedOrigins": [
258 | "*"
259 | ],
260 | "ExposedHeaders": [
261 | "x-amz-server-side-encryption",
262 | "x-amz-request-id",
263 | "x-amz-id-2",
264 | "ETag"
265 | ],
266 | "Id": "S3CORSRuleId1",
267 | "MaxAge": "3000"
268 | }
269 | ]
270 | }
271 | }
272 | },
273 |
274 |
275 | "TriggerPermissions": {
276 | "Type": "AWS::Lambda::Permission",
277 | "Properties": {
278 | "Action": "lambda:InvokeFunction",
279 | "FunctionName": {
280 | "Ref": "functionS3Triggerb01fd26eName"
281 | },
282 | "Principal": "s3.amazonaws.com",
283 | "SourceAccount": {
284 | "Ref": "AWS::AccountId"
285 | },
286 | "SourceArn": {
287 | "Fn::Join": [
288 | "",
289 | [
290 | "arn:aws:s3:::",
291 | {
292 | "Fn::If": [
293 | "ShouldNotCreateEnvResources",
294 | {
295 | "Ref": "bucketName"
296 | },
297 | {
298 | "Fn::Join": [
299 | "",
300 | [
301 | {
302 | "Ref": "bucketName"
303 | },
304 | {
305 | "Fn::Select": [
306 | 3,
307 | {
308 | "Fn::Split": [
309 | "-",
310 | {
311 | "Ref": "AWS::StackName"
312 | }
313 | ]
314 | }
315 | ]
316 | },
317 | "-",
318 | {
319 | "Ref": "env"
320 | }
321 | ]
322 | ]
323 | }
324 | ]
325 | }
326 | ]
327 | ]
328 | }
329 | }
330 | },
331 | "S3TriggerBucketPolicy": {
332 | "DependsOn": [
333 | "S3Bucket"
334 | ],
335 | "Type": "AWS::IAM::Policy",
336 | "Properties": {
337 | "PolicyName": "amplify-lambda-execution-policy",
338 | "Roles": [
339 | {
340 | "Ref": "functionS3Triggerb01fd26eLambdaExecutionRole"
341 | }
342 | ],
343 | "PolicyDocument": {
344 | "Version": "2012-10-17",
345 | "Statement": [
346 | {
347 | "Effect": "Allow",
348 | "Action": [
349 | "s3:PutObject",
350 | "s3:GetObject",
351 | "s3:ListBucket",
352 | "s3:DeleteObject"
353 | ],
354 | "Resource": [
355 | {
356 | "Fn::Join": [
357 | "",
358 | [
359 | "arn:aws:s3:::",
360 | {
361 | "Ref": "S3Bucket"
362 | },
363 | "/*"
364 | ]
365 | ]
366 | }
367 | ]
368 | }
369 | ]
370 | }
371 | }
372 | },
373 |
374 | "S3AuthPublicPolicy": {
375 | "DependsOn": [
376 | "S3Bucket"
377 | ],
378 | "Condition": "CreateAuthPublic",
379 | "Type": "AWS::IAM::Policy",
380 | "Properties": {
381 | "PolicyName": {
382 | "Ref": "s3PublicPolicy"
383 | },
384 | "Roles": [
385 | {
386 | "Ref": "authRoleName"
387 | }
388 | ],
389 | "PolicyDocument": {
390 | "Version": "2012-10-17",
391 | "Statement": [
392 | {
393 | "Effect": "Allow",
394 | "Action": {
395 | "Fn::Split" : [ "," , {
396 | "Ref": "s3PermissionsAuthenticatedPublic"
397 | } ]
398 | },
399 | "Resource": [
400 | {
401 | "Fn::Join": [
402 | "",
403 | [
404 | "arn:aws:s3:::",
405 | {
406 | "Ref": "S3Bucket"
407 | },
408 | "/public/*"
409 | ]
410 | ]
411 | }
412 | ]
413 | }
414 | ]
415 | }
416 | }
417 | },
418 | "S3AuthProtectedPolicy": {
419 | "DependsOn": [
420 | "S3Bucket"
421 | ],
422 | "Condition": "CreateAuthProtected",
423 | "Type": "AWS::IAM::Policy",
424 | "Properties": {
425 | "PolicyName": {
426 | "Ref": "s3ProtectedPolicy"
427 | },
428 | "Roles": [
429 | {
430 | "Ref": "authRoleName"
431 | }
432 | ],
433 | "PolicyDocument": {
434 | "Version": "2012-10-17",
435 | "Statement": [
436 | {
437 | "Effect": "Allow",
438 | "Action": {
439 | "Fn::Split" : [ "," , {
440 | "Ref": "s3PermissionsAuthenticatedProtected"
441 | } ]
442 | },
443 | "Resource": [
444 | {
445 | "Fn::Join": [
446 | "",
447 | [
448 | "arn:aws:s3:::",
449 | {
450 | "Ref": "S3Bucket"
451 | },
452 | "/protected/${cognito-identity.amazonaws.com:sub}/*"
453 | ]
454 | ]
455 | }
456 | ]
457 | }
458 | ]
459 | }
460 | }
461 | },
462 | "S3AuthPrivatePolicy": {
463 | "DependsOn": [
464 | "S3Bucket"
465 | ],
466 | "Condition": "CreateAuthPrivate",
467 | "Type": "AWS::IAM::Policy",
468 | "Properties": {
469 | "PolicyName": {
470 | "Ref": "s3PrivatePolicy"
471 | },
472 | "Roles": [
473 | {
474 | "Ref": "authRoleName"
475 | }
476 | ],
477 | "PolicyDocument": {
478 | "Version": "2012-10-17",
479 | "Statement": [
480 | {
481 | "Effect": "Allow",
482 | "Action": {
483 | "Fn::Split" : [ "," , {
484 | "Ref": "s3PermissionsAuthenticatedPrivate"
485 | } ]
486 | },
487 | "Resource": [
488 | {
489 | "Fn::Join": [
490 | "",
491 | [
492 | "arn:aws:s3:::",
493 | {
494 | "Ref": "S3Bucket"
495 | },
496 | "/private/${cognito-identity.amazonaws.com:sub}/*"
497 | ]
498 | ]
499 | }
500 | ]
501 | }
502 | ]
503 | }
504 | }
505 | },
506 | "S3AuthUploadPolicy": {
507 | "DependsOn": [
508 | "S3Bucket"
509 | ],
510 | "Condition": "CreateAuthUploads",
511 | "Type": "AWS::IAM::Policy",
512 | "Properties": {
513 | "PolicyName": {
514 | "Ref": "s3UploadsPolicy"
515 | },
516 | "Roles": [
517 | {
518 | "Ref": "authRoleName"
519 | }
520 | ],
521 | "PolicyDocument": {
522 | "Version": "2012-10-17",
523 | "Statement": [
524 | {
525 | "Effect": "Allow",
526 | "Action": {
527 | "Fn::Split" : [ "," , {
528 | "Ref": "s3PermissionsAuthenticatedUploads"
529 | } ]
530 | },
531 | "Resource": [
532 | {
533 | "Fn::Join": [
534 | "",
535 | [
536 | "arn:aws:s3:::",
537 | {
538 | "Ref": "S3Bucket"
539 | },
540 | "/uploads/*"
541 | ]
542 | ]
543 | }
544 | ]
545 | }
546 | ]
547 | }
548 | }
549 | },
550 | "S3AuthReadPolicy": {
551 | "DependsOn": [
552 | "S3Bucket"
553 | ],
554 | "Condition": "AuthReadAndList",
555 | "Type": "AWS::IAM::Policy",
556 | "Properties": {
557 | "PolicyName": {
558 | "Ref": "s3ReadPolicy"
559 | },
560 | "Roles": [
561 | {
562 | "Ref": "authRoleName"
563 | }
564 | ],
565 | "PolicyDocument": {
566 | "Version": "2012-10-17",
567 | "Statement": [
568 | {
569 | "Effect": "Allow",
570 | "Action": [
571 | "s3:GetObject"
572 | ],
573 | "Resource": [
574 | {
575 | "Fn::Join": [
576 | "",
577 | [
578 | "arn:aws:s3:::",
579 | {
580 | "Ref": "S3Bucket"
581 | },
582 | "/protected/*"
583 | ]
584 | ]
585 | }
586 | ]
587 | },
588 | {
589 | "Effect": "Allow",
590 | "Action": [
591 | "s3:ListBucket"
592 | ],
593 | "Resource": [
594 | {
595 | "Fn::Join": [
596 | "",
597 | [
598 | "arn:aws:s3:::",
599 | {
600 | "Ref": "S3Bucket"
601 | }
602 | ]
603 | ]
604 | }
605 | ],
606 | "Condition": {
607 | "StringLike": {
608 | "s3:prefix": [
609 | "public/",
610 | "public/*",
611 | "protected/",
612 | "protected/*",
613 | "private/${cognito-identity.amazonaws.com:sub}/",
614 | "private/${cognito-identity.amazonaws.com:sub}/*"
615 | ]
616 | }
617 | }
618 | }
619 | ]
620 | }
621 | }
622 | },
623 | "S3GuestPublicPolicy": {
624 | "DependsOn": [
625 | "S3Bucket"
626 | ],
627 | "Condition": "CreateGuestPublic",
628 | "Type": "AWS::IAM::Policy",
629 | "Properties": {
630 | "PolicyName": {
631 | "Ref": "s3PublicPolicy"
632 | },
633 | "Roles": [
634 | {
635 | "Ref": "unauthRoleName"
636 | }
637 | ],
638 | "PolicyDocument": {
639 | "Version": "2012-10-17",
640 | "Statement": [
641 | {
642 | "Effect": "Allow",
643 | "Action": {
644 | "Fn::Split" : [ "," , {
645 | "Ref": "s3PermissionsGuestPublic"
646 | } ]
647 | },
648 | "Resource": [
649 | {
650 | "Fn::Join": [
651 | "",
652 | [
653 | "arn:aws:s3:::",
654 | {
655 | "Ref": "S3Bucket"
656 | },
657 | "/public/*"
658 | ]
659 | ]
660 | }
661 | ]
662 | }
663 | ]
664 | }
665 | }
666 | },
667 | "S3GuestUploadPolicy": {
668 | "DependsOn": [
669 | "S3Bucket"
670 | ],
671 | "Condition": "CreateGuestUploads",
672 | "Type": "AWS::IAM::Policy",
673 | "Properties": {
674 | "PolicyName": {
675 | "Ref": "s3UploadsPolicy"
676 | },
677 | "Roles": [
678 | {
679 | "Ref": "unauthRoleName"
680 | }
681 | ],
682 | "PolicyDocument": {
683 | "Version": "2012-10-17",
684 | "Statement": [
685 | {
686 | "Effect": "Allow",
687 | "Action": {
688 | "Fn::Split" : [ "," , {
689 | "Ref": "s3PermissionsGuestUploads"
690 | } ]
691 | },
692 | "Resource": [
693 | {
694 | "Fn::Join": [
695 | "",
696 | [
697 | "arn:aws:s3:::",
698 | {
699 | "Ref": "S3Bucket"
700 | },
701 | "/uploads/*"
702 | ]
703 | ]
704 | }
705 | ]
706 | }
707 | ]
708 | }
709 | }
710 | },
711 | "S3GuestReadPolicy": {
712 | "DependsOn": [
713 | "S3Bucket"
714 | ],
715 | "Condition": "GuestReadAndList",
716 | "Type": "AWS::IAM::Policy",
717 | "Properties": {
718 | "PolicyName": {
719 | "Ref": "s3ReadPolicy"
720 | },
721 | "Roles": [
722 | {
723 | "Ref": "unauthRoleName"
724 | }
725 | ],
726 | "PolicyDocument": {
727 | "Version": "2012-10-17",
728 | "Statement": [
729 | {
730 | "Effect": "Allow",
731 | "Action": [
732 | "s3:GetObject"
733 | ],
734 | "Resource": [
735 | {
736 | "Fn::Join": [
737 | "",
738 | [
739 | "arn:aws:s3:::",
740 | {
741 | "Ref": "S3Bucket"
742 | },
743 | "/protected/*"
744 | ]
745 | ]
746 | }
747 | ]
748 | },
749 | {
750 | "Effect": "Allow",
751 | "Action": [
752 | "s3:ListBucket"
753 | ],
754 | "Resource": [
755 | {
756 | "Fn::Join": [
757 | "",
758 | [
759 | "arn:aws:s3:::",
760 | {
761 | "Ref": "S3Bucket"
762 | }
763 | ]
764 | ]
765 | }
766 | ],
767 | "Condition": {
768 | "StringLike": {
769 | "s3:prefix": [
770 | "public/",
771 | "public/*",
772 | "protected/",
773 | "protected/*"
774 | ]
775 | }
776 | }
777 | }
778 | ]
779 | }
780 | }
781 | }
782 | },
783 | "Outputs": {
784 | "BucketName": {
785 | "Value": {
786 | "Ref": "S3Bucket"
787 | },
788 | "Description": "Bucket name for the S3 bucket"
789 | },
790 | "Region": {
791 | "Value": {
792 | "Ref": "AWS::Region"
793 | }
794 | }
795 | }
796 | }
797 |
--------------------------------------------------------------------------------
/amplify/backend/storage/photoalbumsstorage/storage-params.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/amplify/team-provider-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev": {
3 | "awscloudformation": {
4 | "AuthRoleName": "amplify-photoalbums-dev-193536-authRole",
5 | "UnauthRoleArn": "arn:aws:iam::534592782540:role/amplify-photoalbums-dev-193536-unauthRole",
6 | "AuthRoleArn": "arn:aws:iam::534592782540:role/amplify-photoalbums-dev-193536-authRole",
7 | "Region": "us-east-1",
8 | "DeploymentBucketName": "amplify-photoalbums-dev-193536-deployment",
9 | "UnauthRoleName": "amplify-photoalbums-dev-193536-unauthRole",
10 | "StackName": "amplify-photoalbums-dev-193536",
11 | "StackId": "arn:aws:cloudformation:us-east-1:534592782540:stack/amplify-photoalbums-dev-193536/fe351d70-2285-11ea-ab9d-0ed5acf75591",
12 | "AmplifyAppId": "d3ea1382tw73ek"
13 | },
14 | "categories": {
15 | "auth": {
16 | "photoalbums9240b8c0": {}
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "photoalbums",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@aws-amplify/api": "^2.1.2",
7 | "@aws-amplify/core": "^2.2.1",
8 | "@aws-amplify/datastore": "^1.0.3",
9 | "@testing-library/jest-dom": "^4.2.4",
10 | "@testing-library/react": "^9.4.0",
11 | "@testing-library/user-event": "^7.2.1",
12 | "aws-amplify": "^2.2.1",
13 | "aws-amplify-react": "^3.1.2",
14 | "lodash": "^4.17.15",
15 | "react": "^16.12.0",
16 | "react-dom": "^16.12.0",
17 | "react-router-dom": "^5.1.2",
18 | "react-scripts": "3.3.0",
19 | "uuid": "^3.3.3"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rakannimer/amplify-react-workshop/17fdf010d86ee1b86b4aaaab2d13d3e13b535d0b/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | Photo Albums
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rakannimer/amplify-react-workshop/17fdf010d86ee1b86b4aaaab2d13d3e13b535d0b/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rakannimer/amplify-react-workshop/17fdf010d86ee1b86b4aaaab2d13d3e13b535d0b/public/logo512.png
--------------------------------------------------------------------------------
/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Grid,
4 | Header,
5 | Input,
6 | List,
7 | Segment,
8 | Form,
9 | Divider
10 | } from "semantic-ui-react";
11 | import { BrowserRouter as Router, Route, NavLink } from "react-router-dom";
12 | import sortBy from "lodash/sortBy";
13 | import { withAuthenticator, S3Image } from "aws-amplify-react";
14 | import { createAlbum, createPhoto } from "./graphql/mutations";
15 | import { onCreateAlbum, onCreatePhoto } from "./graphql/subscriptions";
16 | import { listAlbums, getAlbum, convertImageToText } from "./graphql/queries";
17 | import API, { graphqlOperation } from "@aws-amplify/api";
18 | import Amplify, { Auth, Storage } from "aws-amplify";
19 | import { v4 as uuid } from "uuid";
20 |
21 | import awsconfig from "./aws-exports";
22 |
23 | Amplify.Logger.LOG_LEVEL = "DEBUG";
24 |
25 | Amplify.configure(awsconfig);
26 |
27 | const uploadFile = async (event, albumId, username) => {
28 | const {
29 | target: { value, files }
30 | } = event;
31 | const fileForUpload = files[0];
32 | const file = fileForUpload || value;
33 | const extension = file.name.split(".")[1];
34 | const { type: mimeType } = file;
35 | const key = `images/${uuid()}${albumId}.${extension}`;
36 | let s3Obj;
37 | try {
38 | s3Obj = await Storage.put(key, file, {
39 | contentType: mimeType,
40 | metadata: {
41 | owner: username,
42 | albumId
43 | }
44 | });
45 | console.log("successfully uploaded image!");
46 | } catch (err) {
47 | console.log("error: ", err);
48 | return;
49 | }
50 | const s3ImageKey = s3Obj.key;
51 | const predictionResult = await API.graphql(
52 | graphqlOperation(convertImageToText, {
53 | input: {
54 | identifyLabels: {
55 | key: s3ImageKey
56 | }
57 | }
58 | })
59 | );
60 | const imageLabels = predictionResult.data.convertImageToText;
61 | console.warn({ imageLabels });
62 |
63 | await API.graphql(
64 | graphqlOperation(createPhoto, {
65 | input: {
66 | bucket: awsconfig.aws_user_files_s3_bucket,
67 | name: key,
68 | createdAt: `${Date.now()}`,
69 | photoAlbumId: albumId,
70 | labels: imageLabels
71 | }
72 | })
73 | );
74 | };
75 |
76 | const S3ImageUpload = ({ albumId }) => {
77 | const { username } = React.useContext(UserContext);
78 | const [isUploading, setIsUploading] = React.useState(false);
79 | const onChange = async event => {
80 | setIsUploading(true);
81 |
82 | let files = [];
83 | for (var i = 0; i < event.target.files.length; i++) {
84 | files.push(event.target.files.item(i));
85 | }
86 | await Promise.all(files.map(f => uploadFile(event, albumId, username)));
87 |
88 | setIsUploading(false);
89 | };
90 | return (
91 |
92 |
document.getElementById("add-image-file-input").click()}
94 | disabled={isUploading}
95 | icon="file image outline"
96 | content={isUploading ? "Uploading..." : "Add Images"}
97 | />
98 |
106 |
107 | );
108 | };
109 |
110 | const NewAlbum = () => {
111 | const [albumName, setAlbumName] = React.useState("");
112 | const handleSubmit = async () => {
113 | console.log(`Creating album ${albumName} `);
114 | await API.graphql(
115 | graphqlOperation(createAlbum, {
116 | input: {
117 | name: albumName,
118 | createdAt: `${Date.now()}`
119 | }
120 | })
121 | );
122 | setAlbumName("");
123 | };
124 | const handleChange = event => {
125 | setAlbumName(event.target.value);
126 | };
127 | return (
128 |
129 |
130 |
140 |
141 | );
142 | };
143 |
144 | const AlbumsList = ({ albums = [] }) => {
145 | return (
146 |
147 |
148 |
149 | {sortBy(albums, ["createdAt"]).map(album => (
150 |
151 | {album.name}
152 |
153 | ))}
154 |
155 |
156 | );
157 | };
158 | const AlbumDetailsLoader = ({ id }) => {
159 | const { username } = React.useContext(UserContext);
160 | const [isLoading, setIsLoading] = React.useState(false);
161 | const [album, setAlbum] = React.useState({});
162 |
163 | React.useEffect(() => {
164 | let isMounted = true;
165 | setIsLoading(true);
166 | API.graphql(graphqlOperation(getAlbum, { id })).then(albumDetails => {
167 | if (!isMounted) return;
168 | setIsLoading(false);
169 | setAlbum(albumDetails.data.getAlbum);
170 | });
171 | API.graphql(graphqlOperation(onCreatePhoto, { owner: username })).subscribe(
172 | photo => {
173 | const newPhoto = photo.value.data.onCreatePhoto;
174 | setAlbum(alb => {
175 | return { ...alb, photos: { items: [newPhoto, ...alb.photos.items] } };
176 | });
177 | }
178 | );
179 | return () => {
180 | isMounted = false;
181 | };
182 | }, [id, username]);
183 |
184 | if (isLoading) {
185 | return Loading...
;
186 | }
187 | return ;
188 | };
189 |
190 | const AlbumDetails = ({ album }) => {
191 | return (
192 |
193 |
194 |
195 |
196 |
197 | );
198 | };
199 |
200 | const AlbumsListLoader = () => {
201 | const [isLoading, setIsLoading] = React.useState(true);
202 | const [albums, setAlbums] = React.useState([]);
203 | const { username } = React.useContext(UserContext);
204 | React.useEffect(() => {
205 | let isMounted = true;
206 | if (!username) return;
207 | setIsLoading(true);
208 | API.graphql(graphqlOperation(listAlbums)).then(albs => {
209 | if (!isMounted) return;
210 | setAlbums(albs.data.listAlbums.items);
211 | setIsLoading(false);
212 | });
213 | API.graphql(graphqlOperation(onCreateAlbum, { owner: username })).subscribe(
214 | newAlbum => {
215 | const albumRecord = newAlbum.value.data.onCreateAlbum;
216 | setAlbums(albs => [...albs, albumRecord]);
217 | }
218 | );
219 | return () => {
220 | isMounted = false;
221 | };
222 | }, [username]);
223 | if (isLoading) return null;
224 | return ;
225 | };
226 |
227 | const PhotosList = ({ photos }) => {
228 | return (
229 |
230 |
231 | {photos &&
232 | photos.items &&
233 | photos.items.map(photo => (
234 |
235 |
245 | {photo.labels && `Labels : ${photo.labels.join(" ")}`}
246 |
247 | ))}
248 |
249 | );
250 | };
251 |
252 | const UserContext = React.createContext({ username: null });
253 |
254 | const App = () => {
255 | const [user, setUser] = React.useState({ username: null });
256 | React.useEffect(() => {
257 | Auth.currentAuthenticatedUser().then(user => {
258 | setUser(user);
259 | });
260 | }, []);
261 | return (
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 | (
272 |
273 | Back to Albums list
274 |
275 | )}
276 | />
277 | (
280 |
281 | )}
282 | />
283 |
284 |
285 |
286 |
287 | );
288 | };
289 |
290 | export default withAuthenticator(App, { includeGreetings: true });
291 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/graphql/mutations.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const createAlbum = `mutation CreateAlbum(
5 | $input: CreateAlbumInput!
6 | $condition: ModelAlbumConditionInput
7 | ) {
8 | createAlbum(input: $input, condition: $condition) {
9 | id
10 | name
11 | createdAt
12 | photos {
13 | items {
14 | id
15 | bucket
16 | name
17 | createdAt
18 | labels
19 | owner
20 | }
21 | nextToken
22 | }
23 | owner
24 | }
25 | }
26 | `;
27 | export const updateAlbum = `mutation UpdateAlbum(
28 | $input: UpdateAlbumInput!
29 | $condition: ModelAlbumConditionInput
30 | ) {
31 | updateAlbum(input: $input, condition: $condition) {
32 | id
33 | name
34 | createdAt
35 | photos {
36 | items {
37 | id
38 | bucket
39 | name
40 | createdAt
41 | labels
42 | owner
43 | }
44 | nextToken
45 | }
46 | owner
47 | }
48 | }
49 | `;
50 | export const deleteAlbum = `mutation DeleteAlbum(
51 | $input: DeleteAlbumInput!
52 | $condition: ModelAlbumConditionInput
53 | ) {
54 | deleteAlbum(input: $input, condition: $condition) {
55 | id
56 | name
57 | createdAt
58 | photos {
59 | items {
60 | id
61 | bucket
62 | name
63 | createdAt
64 | labels
65 | owner
66 | }
67 | nextToken
68 | }
69 | owner
70 | }
71 | }
72 | `;
73 | export const createPhoto = `mutation CreatePhoto(
74 | $input: CreatePhotoInput!
75 | $condition: ModelPhotoConditionInput
76 | ) {
77 | createPhoto(input: $input, condition: $condition) {
78 | id
79 | album {
80 | id
81 | name
82 | createdAt
83 | photos {
84 | nextToken
85 | }
86 | owner
87 | }
88 | bucket
89 | name
90 | createdAt
91 | labels
92 | owner
93 | }
94 | }
95 | `;
96 | export const updatePhoto = `mutation UpdatePhoto(
97 | $input: UpdatePhotoInput!
98 | $condition: ModelPhotoConditionInput
99 | ) {
100 | updatePhoto(input: $input, condition: $condition) {
101 | id
102 | album {
103 | id
104 | name
105 | createdAt
106 | photos {
107 | nextToken
108 | }
109 | owner
110 | }
111 | bucket
112 | name
113 | createdAt
114 | labels
115 | owner
116 | }
117 | }
118 | `;
119 | export const deletePhoto = `mutation DeletePhoto(
120 | $input: DeletePhotoInput!
121 | $condition: ModelPhotoConditionInput
122 | ) {
123 | deletePhoto(input: $input, condition: $condition) {
124 | id
125 | album {
126 | id
127 | name
128 | createdAt
129 | photos {
130 | nextToken
131 | }
132 | owner
133 | }
134 | bucket
135 | name
136 | createdAt
137 | labels
138 | owner
139 | }
140 | }
141 | `;
142 |
--------------------------------------------------------------------------------
/src/graphql/queries.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const getAlbum = `query GetAlbum($id: ID!) {
5 | getAlbum(id: $id) {
6 | id
7 | name
8 | createdAt
9 | photos {
10 | items {
11 | id
12 | bucket
13 | name
14 | createdAt
15 | labels
16 | owner
17 | }
18 | nextToken
19 | }
20 | owner
21 | }
22 | }
23 | `;
24 | export const listAlbums = `query ListAlbums(
25 | $filter: ModelAlbumFilterInput
26 | $limit: Int
27 | $nextToken: String
28 | ) {
29 | listAlbums(filter: $filter, limit: $limit, nextToken: $nextToken) {
30 | items {
31 | id
32 | name
33 | createdAt
34 | photos {
35 | nextToken
36 | }
37 | owner
38 | }
39 | nextToken
40 | }
41 | }
42 | `;
43 | export const getPhoto = `query GetPhoto($id: ID!) {
44 | getPhoto(id: $id) {
45 | id
46 | album {
47 | id
48 | name
49 | createdAt
50 | photos {
51 | nextToken
52 | }
53 | owner
54 | }
55 | bucket
56 | name
57 | createdAt
58 | labels
59 | owner
60 | }
61 | }
62 | `;
63 | export const listPhotos = `query ListPhotos(
64 | $filter: ModelPhotoFilterInput
65 | $limit: Int
66 | $nextToken: String
67 | ) {
68 | listPhotos(filter: $filter, limit: $limit, nextToken: $nextToken) {
69 | items {
70 | id
71 | album {
72 | id
73 | name
74 | createdAt
75 | owner
76 | }
77 | bucket
78 | name
79 | createdAt
80 | labels
81 | owner
82 | }
83 | nextToken
84 | }
85 | }
86 | `;
87 | export const convertImageToText = `query ConvertImageToText($input: ConvertImageToTextInput!) {
88 | convertImageToText(input: $input)
89 | }
90 | `;
91 |
--------------------------------------------------------------------------------
/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" : "getAlbum",
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" : "Album",
37 | "ofType" : null
38 | },
39 | "isDeprecated" : false,
40 | "deprecationReason" : null
41 | }, {
42 | "name" : "listAlbums",
43 | "description" : null,
44 | "args" : [ {
45 | "name" : "filter",
46 | "description" : null,
47 | "type" : {
48 | "kind" : "INPUT_OBJECT",
49 | "name" : "ModelAlbumFilterInput",
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" : "ModelAlbumConnection",
75 | "ofType" : null
76 | },
77 | "isDeprecated" : false,
78 | "deprecationReason" : null
79 | }, {
80 | "name" : "getPhoto",
81 | "description" : null,
82 | "args" : [ {
83 | "name" : "id",
84 | "description" : null,
85 | "type" : {
86 | "kind" : "NON_NULL",
87 | "name" : null,
88 | "ofType" : {
89 | "kind" : "SCALAR",
90 | "name" : "ID",
91 | "ofType" : null
92 | }
93 | },
94 | "defaultValue" : null
95 | } ],
96 | "type" : {
97 | "kind" : "OBJECT",
98 | "name" : "Photo",
99 | "ofType" : null
100 | },
101 | "isDeprecated" : false,
102 | "deprecationReason" : null
103 | }, {
104 | "name" : "listPhotos",
105 | "description" : null,
106 | "args" : [ {
107 | "name" : "filter",
108 | "description" : null,
109 | "type" : {
110 | "kind" : "INPUT_OBJECT",
111 | "name" : "ModelPhotoFilterInput",
112 | "ofType" : null
113 | },
114 | "defaultValue" : null
115 | }, {
116 | "name" : "limit",
117 | "description" : null,
118 | "type" : {
119 | "kind" : "SCALAR",
120 | "name" : "Int",
121 | "ofType" : null
122 | },
123 | "defaultValue" : null
124 | }, {
125 | "name" : "nextToken",
126 | "description" : null,
127 | "type" : {
128 | "kind" : "SCALAR",
129 | "name" : "String",
130 | "ofType" : null
131 | },
132 | "defaultValue" : null
133 | } ],
134 | "type" : {
135 | "kind" : "OBJECT",
136 | "name" : "ModelPhotoConnection",
137 | "ofType" : null
138 | },
139 | "isDeprecated" : false,
140 | "deprecationReason" : null
141 | }, {
142 | "name" : "convertImageToText",
143 | "description" : null,
144 | "args" : [ {
145 | "name" : "input",
146 | "description" : null,
147 | "type" : {
148 | "kind" : "NON_NULL",
149 | "name" : null,
150 | "ofType" : {
151 | "kind" : "INPUT_OBJECT",
152 | "name" : "ConvertImageToTextInput",
153 | "ofType" : null
154 | }
155 | },
156 | "defaultValue" : null
157 | } ],
158 | "type" : {
159 | "kind" : "LIST",
160 | "name" : null,
161 | "ofType" : {
162 | "kind" : "SCALAR",
163 | "name" : "String",
164 | "ofType" : null
165 | }
166 | },
167 | "isDeprecated" : false,
168 | "deprecationReason" : null
169 | } ],
170 | "inputFields" : null,
171 | "interfaces" : [ ],
172 | "enumValues" : null,
173 | "possibleTypes" : null
174 | }, {
175 | "kind" : "OBJECT",
176 | "name" : "Album",
177 | "description" : null,
178 | "fields" : [ {
179 | "name" : "id",
180 | "description" : null,
181 | "args" : [ ],
182 | "type" : {
183 | "kind" : "NON_NULL",
184 | "name" : null,
185 | "ofType" : {
186 | "kind" : "SCALAR",
187 | "name" : "ID",
188 | "ofType" : null
189 | }
190 | },
191 | "isDeprecated" : false,
192 | "deprecationReason" : null
193 | }, {
194 | "name" : "name",
195 | "description" : null,
196 | "args" : [ ],
197 | "type" : {
198 | "kind" : "NON_NULL",
199 | "name" : null,
200 | "ofType" : {
201 | "kind" : "SCALAR",
202 | "name" : "String",
203 | "ofType" : null
204 | }
205 | },
206 | "isDeprecated" : false,
207 | "deprecationReason" : null
208 | }, {
209 | "name" : "createdAt",
210 | "description" : null,
211 | "args" : [ ],
212 | "type" : {
213 | "kind" : "NON_NULL",
214 | "name" : null,
215 | "ofType" : {
216 | "kind" : "SCALAR",
217 | "name" : "String",
218 | "ofType" : null
219 | }
220 | },
221 | "isDeprecated" : false,
222 | "deprecationReason" : null
223 | }, {
224 | "name" : "photos",
225 | "description" : null,
226 | "args" : [ {
227 | "name" : "filter",
228 | "description" : null,
229 | "type" : {
230 | "kind" : "INPUT_OBJECT",
231 | "name" : "ModelPhotoFilterInput",
232 | "ofType" : null
233 | },
234 | "defaultValue" : null
235 | }, {
236 | "name" : "sortDirection",
237 | "description" : null,
238 | "type" : {
239 | "kind" : "ENUM",
240 | "name" : "ModelSortDirection",
241 | "ofType" : null
242 | },
243 | "defaultValue" : null
244 | }, {
245 | "name" : "limit",
246 | "description" : null,
247 | "type" : {
248 | "kind" : "SCALAR",
249 | "name" : "Int",
250 | "ofType" : null
251 | },
252 | "defaultValue" : null
253 | }, {
254 | "name" : "nextToken",
255 | "description" : null,
256 | "type" : {
257 | "kind" : "SCALAR",
258 | "name" : "String",
259 | "ofType" : null
260 | },
261 | "defaultValue" : null
262 | } ],
263 | "type" : {
264 | "kind" : "OBJECT",
265 | "name" : "ModelPhotoConnection",
266 | "ofType" : null
267 | },
268 | "isDeprecated" : false,
269 | "deprecationReason" : null
270 | }, {
271 | "name" : "owner",
272 | "description" : null,
273 | "args" : [ ],
274 | "type" : {
275 | "kind" : "SCALAR",
276 | "name" : "String",
277 | "ofType" : null
278 | },
279 | "isDeprecated" : false,
280 | "deprecationReason" : null
281 | } ],
282 | "inputFields" : null,
283 | "interfaces" : [ ],
284 | "enumValues" : null,
285 | "possibleTypes" : null
286 | }, {
287 | "kind" : "SCALAR",
288 | "name" : "ID",
289 | "description" : "Built-in ID",
290 | "fields" : null,
291 | "inputFields" : null,
292 | "interfaces" : null,
293 | "enumValues" : null,
294 | "possibleTypes" : null
295 | }, {
296 | "kind" : "SCALAR",
297 | "name" : "String",
298 | "description" : "Built-in String",
299 | "fields" : null,
300 | "inputFields" : null,
301 | "interfaces" : null,
302 | "enumValues" : null,
303 | "possibleTypes" : null
304 | }, {
305 | "kind" : "OBJECT",
306 | "name" : "ModelPhotoConnection",
307 | "description" : null,
308 | "fields" : [ {
309 | "name" : "items",
310 | "description" : null,
311 | "args" : [ ],
312 | "type" : {
313 | "kind" : "LIST",
314 | "name" : null,
315 | "ofType" : {
316 | "kind" : "OBJECT",
317 | "name" : "Photo",
318 | "ofType" : null
319 | }
320 | },
321 | "isDeprecated" : false,
322 | "deprecationReason" : null
323 | }, {
324 | "name" : "nextToken",
325 | "description" : null,
326 | "args" : [ ],
327 | "type" : {
328 | "kind" : "SCALAR",
329 | "name" : "String",
330 | "ofType" : null
331 | },
332 | "isDeprecated" : false,
333 | "deprecationReason" : null
334 | } ],
335 | "inputFields" : null,
336 | "interfaces" : [ ],
337 | "enumValues" : null,
338 | "possibleTypes" : null
339 | }, {
340 | "kind" : "OBJECT",
341 | "name" : "Photo",
342 | "description" : null,
343 | "fields" : [ {
344 | "name" : "id",
345 | "description" : null,
346 | "args" : [ ],
347 | "type" : {
348 | "kind" : "NON_NULL",
349 | "name" : null,
350 | "ofType" : {
351 | "kind" : "SCALAR",
352 | "name" : "ID",
353 | "ofType" : null
354 | }
355 | },
356 | "isDeprecated" : false,
357 | "deprecationReason" : null
358 | }, {
359 | "name" : "album",
360 | "description" : null,
361 | "args" : [ ],
362 | "type" : {
363 | "kind" : "OBJECT",
364 | "name" : "Album",
365 | "ofType" : null
366 | },
367 | "isDeprecated" : false,
368 | "deprecationReason" : null
369 | }, {
370 | "name" : "bucket",
371 | "description" : null,
372 | "args" : [ ],
373 | "type" : {
374 | "kind" : "NON_NULL",
375 | "name" : null,
376 | "ofType" : {
377 | "kind" : "SCALAR",
378 | "name" : "String",
379 | "ofType" : null
380 | }
381 | },
382 | "isDeprecated" : false,
383 | "deprecationReason" : null
384 | }, {
385 | "name" : "name",
386 | "description" : null,
387 | "args" : [ ],
388 | "type" : {
389 | "kind" : "NON_NULL",
390 | "name" : null,
391 | "ofType" : {
392 | "kind" : "SCALAR",
393 | "name" : "String",
394 | "ofType" : null
395 | }
396 | },
397 | "isDeprecated" : false,
398 | "deprecationReason" : null
399 | }, {
400 | "name" : "createdAt",
401 | "description" : null,
402 | "args" : [ ],
403 | "type" : {
404 | "kind" : "NON_NULL",
405 | "name" : null,
406 | "ofType" : {
407 | "kind" : "SCALAR",
408 | "name" : "String",
409 | "ofType" : null
410 | }
411 | },
412 | "isDeprecated" : false,
413 | "deprecationReason" : null
414 | }, {
415 | "name" : "labels",
416 | "description" : null,
417 | "args" : [ ],
418 | "type" : {
419 | "kind" : "LIST",
420 | "name" : null,
421 | "ofType" : {
422 | "kind" : "SCALAR",
423 | "name" : "String",
424 | "ofType" : null
425 | }
426 | },
427 | "isDeprecated" : false,
428 | "deprecationReason" : null
429 | }, {
430 | "name" : "owner",
431 | "description" : null,
432 | "args" : [ ],
433 | "type" : {
434 | "kind" : "SCALAR",
435 | "name" : "String",
436 | "ofType" : null
437 | },
438 | "isDeprecated" : false,
439 | "deprecationReason" : null
440 | } ],
441 | "inputFields" : null,
442 | "interfaces" : [ ],
443 | "enumValues" : null,
444 | "possibleTypes" : null
445 | }, {
446 | "kind" : "INPUT_OBJECT",
447 | "name" : "ModelPhotoFilterInput",
448 | "description" : null,
449 | "fields" : null,
450 | "inputFields" : [ {
451 | "name" : "id",
452 | "description" : null,
453 | "type" : {
454 | "kind" : "INPUT_OBJECT",
455 | "name" : "ModelIDInput",
456 | "ofType" : null
457 | },
458 | "defaultValue" : null
459 | }, {
460 | "name" : "bucket",
461 | "description" : null,
462 | "type" : {
463 | "kind" : "INPUT_OBJECT",
464 | "name" : "ModelStringInput",
465 | "ofType" : null
466 | },
467 | "defaultValue" : null
468 | }, {
469 | "name" : "name",
470 | "description" : null,
471 | "type" : {
472 | "kind" : "INPUT_OBJECT",
473 | "name" : "ModelStringInput",
474 | "ofType" : null
475 | },
476 | "defaultValue" : null
477 | }, {
478 | "name" : "createdAt",
479 | "description" : null,
480 | "type" : {
481 | "kind" : "INPUT_OBJECT",
482 | "name" : "ModelStringInput",
483 | "ofType" : null
484 | },
485 | "defaultValue" : null
486 | }, {
487 | "name" : "labels",
488 | "description" : null,
489 | "type" : {
490 | "kind" : "INPUT_OBJECT",
491 | "name" : "ModelStringInput",
492 | "ofType" : null
493 | },
494 | "defaultValue" : null
495 | }, {
496 | "name" : "and",
497 | "description" : null,
498 | "type" : {
499 | "kind" : "LIST",
500 | "name" : null,
501 | "ofType" : {
502 | "kind" : "INPUT_OBJECT",
503 | "name" : "ModelPhotoFilterInput",
504 | "ofType" : null
505 | }
506 | },
507 | "defaultValue" : null
508 | }, {
509 | "name" : "or",
510 | "description" : null,
511 | "type" : {
512 | "kind" : "LIST",
513 | "name" : null,
514 | "ofType" : {
515 | "kind" : "INPUT_OBJECT",
516 | "name" : "ModelPhotoFilterInput",
517 | "ofType" : null
518 | }
519 | },
520 | "defaultValue" : null
521 | }, {
522 | "name" : "not",
523 | "description" : null,
524 | "type" : {
525 | "kind" : "INPUT_OBJECT",
526 | "name" : "ModelPhotoFilterInput",
527 | "ofType" : null
528 | },
529 | "defaultValue" : null
530 | } ],
531 | "interfaces" : null,
532 | "enumValues" : null,
533 | "possibleTypes" : null
534 | }, {
535 | "kind" : "INPUT_OBJECT",
536 | "name" : "ModelIDInput",
537 | "description" : null,
538 | "fields" : null,
539 | "inputFields" : [ {
540 | "name" : "ne",
541 | "description" : null,
542 | "type" : {
543 | "kind" : "SCALAR",
544 | "name" : "ID",
545 | "ofType" : null
546 | },
547 | "defaultValue" : null
548 | }, {
549 | "name" : "eq",
550 | "description" : null,
551 | "type" : {
552 | "kind" : "SCALAR",
553 | "name" : "ID",
554 | "ofType" : null
555 | },
556 | "defaultValue" : null
557 | }, {
558 | "name" : "le",
559 | "description" : null,
560 | "type" : {
561 | "kind" : "SCALAR",
562 | "name" : "ID",
563 | "ofType" : null
564 | },
565 | "defaultValue" : null
566 | }, {
567 | "name" : "lt",
568 | "description" : null,
569 | "type" : {
570 | "kind" : "SCALAR",
571 | "name" : "ID",
572 | "ofType" : null
573 | },
574 | "defaultValue" : null
575 | }, {
576 | "name" : "ge",
577 | "description" : null,
578 | "type" : {
579 | "kind" : "SCALAR",
580 | "name" : "ID",
581 | "ofType" : null
582 | },
583 | "defaultValue" : null
584 | }, {
585 | "name" : "gt",
586 | "description" : null,
587 | "type" : {
588 | "kind" : "SCALAR",
589 | "name" : "ID",
590 | "ofType" : null
591 | },
592 | "defaultValue" : null
593 | }, {
594 | "name" : "contains",
595 | "description" : null,
596 | "type" : {
597 | "kind" : "SCALAR",
598 | "name" : "ID",
599 | "ofType" : null
600 | },
601 | "defaultValue" : null
602 | }, {
603 | "name" : "notContains",
604 | "description" : null,
605 | "type" : {
606 | "kind" : "SCALAR",
607 | "name" : "ID",
608 | "ofType" : null
609 | },
610 | "defaultValue" : null
611 | }, {
612 | "name" : "between",
613 | "description" : null,
614 | "type" : {
615 | "kind" : "LIST",
616 | "name" : null,
617 | "ofType" : {
618 | "kind" : "SCALAR",
619 | "name" : "ID",
620 | "ofType" : null
621 | }
622 | },
623 | "defaultValue" : null
624 | }, {
625 | "name" : "beginsWith",
626 | "description" : null,
627 | "type" : {
628 | "kind" : "SCALAR",
629 | "name" : "ID",
630 | "ofType" : null
631 | },
632 | "defaultValue" : null
633 | }, {
634 | "name" : "attributeExists",
635 | "description" : null,
636 | "type" : {
637 | "kind" : "SCALAR",
638 | "name" : "Boolean",
639 | "ofType" : null
640 | },
641 | "defaultValue" : null
642 | }, {
643 | "name" : "attributeType",
644 | "description" : null,
645 | "type" : {
646 | "kind" : "ENUM",
647 | "name" : "ModelAttributeTypes",
648 | "ofType" : null
649 | },
650 | "defaultValue" : null
651 | }, {
652 | "name" : "size",
653 | "description" : null,
654 | "type" : {
655 | "kind" : "INPUT_OBJECT",
656 | "name" : "ModelSizeInput",
657 | "ofType" : null
658 | },
659 | "defaultValue" : null
660 | } ],
661 | "interfaces" : null,
662 | "enumValues" : null,
663 | "possibleTypes" : null
664 | }, {
665 | "kind" : "SCALAR",
666 | "name" : "Boolean",
667 | "description" : "Built-in Boolean",
668 | "fields" : null,
669 | "inputFields" : null,
670 | "interfaces" : null,
671 | "enumValues" : null,
672 | "possibleTypes" : null
673 | }, {
674 | "kind" : "ENUM",
675 | "name" : "ModelAttributeTypes",
676 | "description" : null,
677 | "fields" : null,
678 | "inputFields" : null,
679 | "interfaces" : null,
680 | "enumValues" : [ {
681 | "name" : "binary",
682 | "description" : null,
683 | "isDeprecated" : false,
684 | "deprecationReason" : null
685 | }, {
686 | "name" : "binarySet",
687 | "description" : null,
688 | "isDeprecated" : false,
689 | "deprecationReason" : null
690 | }, {
691 | "name" : "bool",
692 | "description" : null,
693 | "isDeprecated" : false,
694 | "deprecationReason" : null
695 | }, {
696 | "name" : "list",
697 | "description" : null,
698 | "isDeprecated" : false,
699 | "deprecationReason" : null
700 | }, {
701 | "name" : "map",
702 | "description" : null,
703 | "isDeprecated" : false,
704 | "deprecationReason" : null
705 | }, {
706 | "name" : "number",
707 | "description" : null,
708 | "isDeprecated" : false,
709 | "deprecationReason" : null
710 | }, {
711 | "name" : "numberSet",
712 | "description" : null,
713 | "isDeprecated" : false,
714 | "deprecationReason" : null
715 | }, {
716 | "name" : "string",
717 | "description" : null,
718 | "isDeprecated" : false,
719 | "deprecationReason" : null
720 | }, {
721 | "name" : "stringSet",
722 | "description" : null,
723 | "isDeprecated" : false,
724 | "deprecationReason" : null
725 | }, {
726 | "name" : "_null",
727 | "description" : null,
728 | "isDeprecated" : false,
729 | "deprecationReason" : null
730 | } ],
731 | "possibleTypes" : null
732 | }, {
733 | "kind" : "INPUT_OBJECT",
734 | "name" : "ModelSizeInput",
735 | "description" : null,
736 | "fields" : null,
737 | "inputFields" : [ {
738 | "name" : "ne",
739 | "description" : null,
740 | "type" : {
741 | "kind" : "SCALAR",
742 | "name" : "Int",
743 | "ofType" : null
744 | },
745 | "defaultValue" : null
746 | }, {
747 | "name" : "eq",
748 | "description" : null,
749 | "type" : {
750 | "kind" : "SCALAR",
751 | "name" : "Int",
752 | "ofType" : null
753 | },
754 | "defaultValue" : null
755 | }, {
756 | "name" : "le",
757 | "description" : null,
758 | "type" : {
759 | "kind" : "SCALAR",
760 | "name" : "Int",
761 | "ofType" : null
762 | },
763 | "defaultValue" : null
764 | }, {
765 | "name" : "lt",
766 | "description" : null,
767 | "type" : {
768 | "kind" : "SCALAR",
769 | "name" : "Int",
770 | "ofType" : null
771 | },
772 | "defaultValue" : null
773 | }, {
774 | "name" : "ge",
775 | "description" : null,
776 | "type" : {
777 | "kind" : "SCALAR",
778 | "name" : "Int",
779 | "ofType" : null
780 | },
781 | "defaultValue" : null
782 | }, {
783 | "name" : "gt",
784 | "description" : null,
785 | "type" : {
786 | "kind" : "SCALAR",
787 | "name" : "Int",
788 | "ofType" : null
789 | },
790 | "defaultValue" : null
791 | }, {
792 | "name" : "between",
793 | "description" : null,
794 | "type" : {
795 | "kind" : "LIST",
796 | "name" : null,
797 | "ofType" : {
798 | "kind" : "SCALAR",
799 | "name" : "Int",
800 | "ofType" : null
801 | }
802 | },
803 | "defaultValue" : null
804 | } ],
805 | "interfaces" : null,
806 | "enumValues" : null,
807 | "possibleTypes" : null
808 | }, {
809 | "kind" : "SCALAR",
810 | "name" : "Int",
811 | "description" : "Built-in Int",
812 | "fields" : null,
813 | "inputFields" : null,
814 | "interfaces" : null,
815 | "enumValues" : null,
816 | "possibleTypes" : null
817 | }, {
818 | "kind" : "INPUT_OBJECT",
819 | "name" : "ModelStringInput",
820 | "description" : null,
821 | "fields" : null,
822 | "inputFields" : [ {
823 | "name" : "ne",
824 | "description" : null,
825 | "type" : {
826 | "kind" : "SCALAR",
827 | "name" : "String",
828 | "ofType" : null
829 | },
830 | "defaultValue" : null
831 | }, {
832 | "name" : "eq",
833 | "description" : null,
834 | "type" : {
835 | "kind" : "SCALAR",
836 | "name" : "String",
837 | "ofType" : null
838 | },
839 | "defaultValue" : null
840 | }, {
841 | "name" : "le",
842 | "description" : null,
843 | "type" : {
844 | "kind" : "SCALAR",
845 | "name" : "String",
846 | "ofType" : null
847 | },
848 | "defaultValue" : null
849 | }, {
850 | "name" : "lt",
851 | "description" : null,
852 | "type" : {
853 | "kind" : "SCALAR",
854 | "name" : "String",
855 | "ofType" : null
856 | },
857 | "defaultValue" : null
858 | }, {
859 | "name" : "ge",
860 | "description" : null,
861 | "type" : {
862 | "kind" : "SCALAR",
863 | "name" : "String",
864 | "ofType" : null
865 | },
866 | "defaultValue" : null
867 | }, {
868 | "name" : "gt",
869 | "description" : null,
870 | "type" : {
871 | "kind" : "SCALAR",
872 | "name" : "String",
873 | "ofType" : null
874 | },
875 | "defaultValue" : null
876 | }, {
877 | "name" : "contains",
878 | "description" : null,
879 | "type" : {
880 | "kind" : "SCALAR",
881 | "name" : "String",
882 | "ofType" : null
883 | },
884 | "defaultValue" : null
885 | }, {
886 | "name" : "notContains",
887 | "description" : null,
888 | "type" : {
889 | "kind" : "SCALAR",
890 | "name" : "String",
891 | "ofType" : null
892 | },
893 | "defaultValue" : null
894 | }, {
895 | "name" : "between",
896 | "description" : null,
897 | "type" : {
898 | "kind" : "LIST",
899 | "name" : null,
900 | "ofType" : {
901 | "kind" : "SCALAR",
902 | "name" : "String",
903 | "ofType" : null
904 | }
905 | },
906 | "defaultValue" : null
907 | }, {
908 | "name" : "beginsWith",
909 | "description" : null,
910 | "type" : {
911 | "kind" : "SCALAR",
912 | "name" : "String",
913 | "ofType" : null
914 | },
915 | "defaultValue" : null
916 | }, {
917 | "name" : "attributeExists",
918 | "description" : null,
919 | "type" : {
920 | "kind" : "SCALAR",
921 | "name" : "Boolean",
922 | "ofType" : null
923 | },
924 | "defaultValue" : null
925 | }, {
926 | "name" : "attributeType",
927 | "description" : null,
928 | "type" : {
929 | "kind" : "ENUM",
930 | "name" : "ModelAttributeTypes",
931 | "ofType" : null
932 | },
933 | "defaultValue" : null
934 | }, {
935 | "name" : "size",
936 | "description" : null,
937 | "type" : {
938 | "kind" : "INPUT_OBJECT",
939 | "name" : "ModelSizeInput",
940 | "ofType" : null
941 | },
942 | "defaultValue" : null
943 | } ],
944 | "interfaces" : null,
945 | "enumValues" : null,
946 | "possibleTypes" : null
947 | }, {
948 | "kind" : "ENUM",
949 | "name" : "ModelSortDirection",
950 | "description" : null,
951 | "fields" : null,
952 | "inputFields" : null,
953 | "interfaces" : null,
954 | "enumValues" : [ {
955 | "name" : "ASC",
956 | "description" : null,
957 | "isDeprecated" : false,
958 | "deprecationReason" : null
959 | }, {
960 | "name" : "DESC",
961 | "description" : null,
962 | "isDeprecated" : false,
963 | "deprecationReason" : null
964 | } ],
965 | "possibleTypes" : null
966 | }, {
967 | "kind" : "OBJECT",
968 | "name" : "ModelAlbumConnection",
969 | "description" : null,
970 | "fields" : [ {
971 | "name" : "items",
972 | "description" : null,
973 | "args" : [ ],
974 | "type" : {
975 | "kind" : "LIST",
976 | "name" : null,
977 | "ofType" : {
978 | "kind" : "OBJECT",
979 | "name" : "Album",
980 | "ofType" : null
981 | }
982 | },
983 | "isDeprecated" : false,
984 | "deprecationReason" : null
985 | }, {
986 | "name" : "nextToken",
987 | "description" : null,
988 | "args" : [ ],
989 | "type" : {
990 | "kind" : "SCALAR",
991 | "name" : "String",
992 | "ofType" : null
993 | },
994 | "isDeprecated" : false,
995 | "deprecationReason" : null
996 | } ],
997 | "inputFields" : null,
998 | "interfaces" : [ ],
999 | "enumValues" : null,
1000 | "possibleTypes" : null
1001 | }, {
1002 | "kind" : "INPUT_OBJECT",
1003 | "name" : "ModelAlbumFilterInput",
1004 | "description" : null,
1005 | "fields" : null,
1006 | "inputFields" : [ {
1007 | "name" : "id",
1008 | "description" : null,
1009 | "type" : {
1010 | "kind" : "INPUT_OBJECT",
1011 | "name" : "ModelIDInput",
1012 | "ofType" : null
1013 | },
1014 | "defaultValue" : null
1015 | }, {
1016 | "name" : "name",
1017 | "description" : null,
1018 | "type" : {
1019 | "kind" : "INPUT_OBJECT",
1020 | "name" : "ModelStringInput",
1021 | "ofType" : null
1022 | },
1023 | "defaultValue" : null
1024 | }, {
1025 | "name" : "createdAt",
1026 | "description" : null,
1027 | "type" : {
1028 | "kind" : "INPUT_OBJECT",
1029 | "name" : "ModelStringInput",
1030 | "ofType" : null
1031 | },
1032 | "defaultValue" : null
1033 | }, {
1034 | "name" : "and",
1035 | "description" : null,
1036 | "type" : {
1037 | "kind" : "LIST",
1038 | "name" : null,
1039 | "ofType" : {
1040 | "kind" : "INPUT_OBJECT",
1041 | "name" : "ModelAlbumFilterInput",
1042 | "ofType" : null
1043 | }
1044 | },
1045 | "defaultValue" : null
1046 | }, {
1047 | "name" : "or",
1048 | "description" : null,
1049 | "type" : {
1050 | "kind" : "LIST",
1051 | "name" : null,
1052 | "ofType" : {
1053 | "kind" : "INPUT_OBJECT",
1054 | "name" : "ModelAlbumFilterInput",
1055 | "ofType" : null
1056 | }
1057 | },
1058 | "defaultValue" : null
1059 | }, {
1060 | "name" : "not",
1061 | "description" : null,
1062 | "type" : {
1063 | "kind" : "INPUT_OBJECT",
1064 | "name" : "ModelAlbumFilterInput",
1065 | "ofType" : null
1066 | },
1067 | "defaultValue" : null
1068 | } ],
1069 | "interfaces" : null,
1070 | "enumValues" : null,
1071 | "possibleTypes" : null
1072 | }, {
1073 | "kind" : "INPUT_OBJECT",
1074 | "name" : "ConvertImageToTextInput",
1075 | "description" : null,
1076 | "fields" : null,
1077 | "inputFields" : [ {
1078 | "name" : "identifyLabels",
1079 | "description" : null,
1080 | "type" : {
1081 | "kind" : "NON_NULL",
1082 | "name" : null,
1083 | "ofType" : {
1084 | "kind" : "INPUT_OBJECT",
1085 | "name" : "ConvertImageToTextIdentifyLabelsInput",
1086 | "ofType" : null
1087 | }
1088 | },
1089 | "defaultValue" : null
1090 | } ],
1091 | "interfaces" : null,
1092 | "enumValues" : null,
1093 | "possibleTypes" : null
1094 | }, {
1095 | "kind" : "INPUT_OBJECT",
1096 | "name" : "ConvertImageToTextIdentifyLabelsInput",
1097 | "description" : null,
1098 | "fields" : null,
1099 | "inputFields" : [ {
1100 | "name" : "key",
1101 | "description" : null,
1102 | "type" : {
1103 | "kind" : "NON_NULL",
1104 | "name" : null,
1105 | "ofType" : {
1106 | "kind" : "SCALAR",
1107 | "name" : "String",
1108 | "ofType" : null
1109 | }
1110 | },
1111 | "defaultValue" : null
1112 | } ],
1113 | "interfaces" : null,
1114 | "enumValues" : null,
1115 | "possibleTypes" : null
1116 | }, {
1117 | "kind" : "OBJECT",
1118 | "name" : "Mutation",
1119 | "description" : null,
1120 | "fields" : [ {
1121 | "name" : "createAlbum",
1122 | "description" : null,
1123 | "args" : [ {
1124 | "name" : "input",
1125 | "description" : null,
1126 | "type" : {
1127 | "kind" : "NON_NULL",
1128 | "name" : null,
1129 | "ofType" : {
1130 | "kind" : "INPUT_OBJECT",
1131 | "name" : "CreateAlbumInput",
1132 | "ofType" : null
1133 | }
1134 | },
1135 | "defaultValue" : null
1136 | }, {
1137 | "name" : "condition",
1138 | "description" : null,
1139 | "type" : {
1140 | "kind" : "INPUT_OBJECT",
1141 | "name" : "ModelAlbumConditionInput",
1142 | "ofType" : null
1143 | },
1144 | "defaultValue" : null
1145 | } ],
1146 | "type" : {
1147 | "kind" : "OBJECT",
1148 | "name" : "Album",
1149 | "ofType" : null
1150 | },
1151 | "isDeprecated" : false,
1152 | "deprecationReason" : null
1153 | }, {
1154 | "name" : "updateAlbum",
1155 | "description" : null,
1156 | "args" : [ {
1157 | "name" : "input",
1158 | "description" : null,
1159 | "type" : {
1160 | "kind" : "NON_NULL",
1161 | "name" : null,
1162 | "ofType" : {
1163 | "kind" : "INPUT_OBJECT",
1164 | "name" : "UpdateAlbumInput",
1165 | "ofType" : null
1166 | }
1167 | },
1168 | "defaultValue" : null
1169 | }, {
1170 | "name" : "condition",
1171 | "description" : null,
1172 | "type" : {
1173 | "kind" : "INPUT_OBJECT",
1174 | "name" : "ModelAlbumConditionInput",
1175 | "ofType" : null
1176 | },
1177 | "defaultValue" : null
1178 | } ],
1179 | "type" : {
1180 | "kind" : "OBJECT",
1181 | "name" : "Album",
1182 | "ofType" : null
1183 | },
1184 | "isDeprecated" : false,
1185 | "deprecationReason" : null
1186 | }, {
1187 | "name" : "deleteAlbum",
1188 | "description" : null,
1189 | "args" : [ {
1190 | "name" : "input",
1191 | "description" : null,
1192 | "type" : {
1193 | "kind" : "NON_NULL",
1194 | "name" : null,
1195 | "ofType" : {
1196 | "kind" : "INPUT_OBJECT",
1197 | "name" : "DeleteAlbumInput",
1198 | "ofType" : null
1199 | }
1200 | },
1201 | "defaultValue" : null
1202 | }, {
1203 | "name" : "condition",
1204 | "description" : null,
1205 | "type" : {
1206 | "kind" : "INPUT_OBJECT",
1207 | "name" : "ModelAlbumConditionInput",
1208 | "ofType" : null
1209 | },
1210 | "defaultValue" : null
1211 | } ],
1212 | "type" : {
1213 | "kind" : "OBJECT",
1214 | "name" : "Album",
1215 | "ofType" : null
1216 | },
1217 | "isDeprecated" : false,
1218 | "deprecationReason" : null
1219 | }, {
1220 | "name" : "createPhoto",
1221 | "description" : null,
1222 | "args" : [ {
1223 | "name" : "input",
1224 | "description" : null,
1225 | "type" : {
1226 | "kind" : "NON_NULL",
1227 | "name" : null,
1228 | "ofType" : {
1229 | "kind" : "INPUT_OBJECT",
1230 | "name" : "CreatePhotoInput",
1231 | "ofType" : null
1232 | }
1233 | },
1234 | "defaultValue" : null
1235 | }, {
1236 | "name" : "condition",
1237 | "description" : null,
1238 | "type" : {
1239 | "kind" : "INPUT_OBJECT",
1240 | "name" : "ModelPhotoConditionInput",
1241 | "ofType" : null
1242 | },
1243 | "defaultValue" : null
1244 | } ],
1245 | "type" : {
1246 | "kind" : "OBJECT",
1247 | "name" : "Photo",
1248 | "ofType" : null
1249 | },
1250 | "isDeprecated" : false,
1251 | "deprecationReason" : null
1252 | }, {
1253 | "name" : "updatePhoto",
1254 | "description" : null,
1255 | "args" : [ {
1256 | "name" : "input",
1257 | "description" : null,
1258 | "type" : {
1259 | "kind" : "NON_NULL",
1260 | "name" : null,
1261 | "ofType" : {
1262 | "kind" : "INPUT_OBJECT",
1263 | "name" : "UpdatePhotoInput",
1264 | "ofType" : null
1265 | }
1266 | },
1267 | "defaultValue" : null
1268 | }, {
1269 | "name" : "condition",
1270 | "description" : null,
1271 | "type" : {
1272 | "kind" : "INPUT_OBJECT",
1273 | "name" : "ModelPhotoConditionInput",
1274 | "ofType" : null
1275 | },
1276 | "defaultValue" : null
1277 | } ],
1278 | "type" : {
1279 | "kind" : "OBJECT",
1280 | "name" : "Photo",
1281 | "ofType" : null
1282 | },
1283 | "isDeprecated" : false,
1284 | "deprecationReason" : null
1285 | }, {
1286 | "name" : "deletePhoto",
1287 | "description" : null,
1288 | "args" : [ {
1289 | "name" : "input",
1290 | "description" : null,
1291 | "type" : {
1292 | "kind" : "NON_NULL",
1293 | "name" : null,
1294 | "ofType" : {
1295 | "kind" : "INPUT_OBJECT",
1296 | "name" : "DeletePhotoInput",
1297 | "ofType" : null
1298 | }
1299 | },
1300 | "defaultValue" : null
1301 | }, {
1302 | "name" : "condition",
1303 | "description" : null,
1304 | "type" : {
1305 | "kind" : "INPUT_OBJECT",
1306 | "name" : "ModelPhotoConditionInput",
1307 | "ofType" : null
1308 | },
1309 | "defaultValue" : null
1310 | } ],
1311 | "type" : {
1312 | "kind" : "OBJECT",
1313 | "name" : "Photo",
1314 | "ofType" : null
1315 | },
1316 | "isDeprecated" : false,
1317 | "deprecationReason" : null
1318 | } ],
1319 | "inputFields" : null,
1320 | "interfaces" : [ ],
1321 | "enumValues" : null,
1322 | "possibleTypes" : null
1323 | }, {
1324 | "kind" : "INPUT_OBJECT",
1325 | "name" : "CreateAlbumInput",
1326 | "description" : null,
1327 | "fields" : null,
1328 | "inputFields" : [ {
1329 | "name" : "id",
1330 | "description" : null,
1331 | "type" : {
1332 | "kind" : "SCALAR",
1333 | "name" : "ID",
1334 | "ofType" : null
1335 | },
1336 | "defaultValue" : null
1337 | }, {
1338 | "name" : "name",
1339 | "description" : null,
1340 | "type" : {
1341 | "kind" : "NON_NULL",
1342 | "name" : null,
1343 | "ofType" : {
1344 | "kind" : "SCALAR",
1345 | "name" : "String",
1346 | "ofType" : null
1347 | }
1348 | },
1349 | "defaultValue" : null
1350 | }, {
1351 | "name" : "createdAt",
1352 | "description" : null,
1353 | "type" : {
1354 | "kind" : "NON_NULL",
1355 | "name" : null,
1356 | "ofType" : {
1357 | "kind" : "SCALAR",
1358 | "name" : "String",
1359 | "ofType" : null
1360 | }
1361 | },
1362 | "defaultValue" : null
1363 | } ],
1364 | "interfaces" : null,
1365 | "enumValues" : null,
1366 | "possibleTypes" : null
1367 | }, {
1368 | "kind" : "INPUT_OBJECT",
1369 | "name" : "ModelAlbumConditionInput",
1370 | "description" : null,
1371 | "fields" : null,
1372 | "inputFields" : [ {
1373 | "name" : "name",
1374 | "description" : null,
1375 | "type" : {
1376 | "kind" : "INPUT_OBJECT",
1377 | "name" : "ModelStringInput",
1378 | "ofType" : null
1379 | },
1380 | "defaultValue" : null
1381 | }, {
1382 | "name" : "createdAt",
1383 | "description" : null,
1384 | "type" : {
1385 | "kind" : "INPUT_OBJECT",
1386 | "name" : "ModelStringInput",
1387 | "ofType" : null
1388 | },
1389 | "defaultValue" : null
1390 | }, {
1391 | "name" : "and",
1392 | "description" : null,
1393 | "type" : {
1394 | "kind" : "LIST",
1395 | "name" : null,
1396 | "ofType" : {
1397 | "kind" : "INPUT_OBJECT",
1398 | "name" : "ModelAlbumConditionInput",
1399 | "ofType" : null
1400 | }
1401 | },
1402 | "defaultValue" : null
1403 | }, {
1404 | "name" : "or",
1405 | "description" : null,
1406 | "type" : {
1407 | "kind" : "LIST",
1408 | "name" : null,
1409 | "ofType" : {
1410 | "kind" : "INPUT_OBJECT",
1411 | "name" : "ModelAlbumConditionInput",
1412 | "ofType" : null
1413 | }
1414 | },
1415 | "defaultValue" : null
1416 | }, {
1417 | "name" : "not",
1418 | "description" : null,
1419 | "type" : {
1420 | "kind" : "INPUT_OBJECT",
1421 | "name" : "ModelAlbumConditionInput",
1422 | "ofType" : null
1423 | },
1424 | "defaultValue" : null
1425 | } ],
1426 | "interfaces" : null,
1427 | "enumValues" : null,
1428 | "possibleTypes" : null
1429 | }, {
1430 | "kind" : "INPUT_OBJECT",
1431 | "name" : "UpdateAlbumInput",
1432 | "description" : null,
1433 | "fields" : null,
1434 | "inputFields" : [ {
1435 | "name" : "id",
1436 | "description" : null,
1437 | "type" : {
1438 | "kind" : "NON_NULL",
1439 | "name" : null,
1440 | "ofType" : {
1441 | "kind" : "SCALAR",
1442 | "name" : "ID",
1443 | "ofType" : null
1444 | }
1445 | },
1446 | "defaultValue" : null
1447 | }, {
1448 | "name" : "name",
1449 | "description" : null,
1450 | "type" : {
1451 | "kind" : "SCALAR",
1452 | "name" : "String",
1453 | "ofType" : null
1454 | },
1455 | "defaultValue" : null
1456 | }, {
1457 | "name" : "createdAt",
1458 | "description" : null,
1459 | "type" : {
1460 | "kind" : "SCALAR",
1461 | "name" : "String",
1462 | "ofType" : null
1463 | },
1464 | "defaultValue" : null
1465 | } ],
1466 | "interfaces" : null,
1467 | "enumValues" : null,
1468 | "possibleTypes" : null
1469 | }, {
1470 | "kind" : "INPUT_OBJECT",
1471 | "name" : "DeleteAlbumInput",
1472 | "description" : null,
1473 | "fields" : null,
1474 | "inputFields" : [ {
1475 | "name" : "id",
1476 | "description" : null,
1477 | "type" : {
1478 | "kind" : "SCALAR",
1479 | "name" : "ID",
1480 | "ofType" : null
1481 | },
1482 | "defaultValue" : null
1483 | } ],
1484 | "interfaces" : null,
1485 | "enumValues" : null,
1486 | "possibleTypes" : null
1487 | }, {
1488 | "kind" : "INPUT_OBJECT",
1489 | "name" : "CreatePhotoInput",
1490 | "description" : null,
1491 | "fields" : null,
1492 | "inputFields" : [ {
1493 | "name" : "id",
1494 | "description" : null,
1495 | "type" : {
1496 | "kind" : "SCALAR",
1497 | "name" : "ID",
1498 | "ofType" : null
1499 | },
1500 | "defaultValue" : null
1501 | }, {
1502 | "name" : "bucket",
1503 | "description" : null,
1504 | "type" : {
1505 | "kind" : "NON_NULL",
1506 | "name" : null,
1507 | "ofType" : {
1508 | "kind" : "SCALAR",
1509 | "name" : "String",
1510 | "ofType" : null
1511 | }
1512 | },
1513 | "defaultValue" : null
1514 | }, {
1515 | "name" : "name",
1516 | "description" : null,
1517 | "type" : {
1518 | "kind" : "NON_NULL",
1519 | "name" : null,
1520 | "ofType" : {
1521 | "kind" : "SCALAR",
1522 | "name" : "String",
1523 | "ofType" : null
1524 | }
1525 | },
1526 | "defaultValue" : null
1527 | }, {
1528 | "name" : "createdAt",
1529 | "description" : null,
1530 | "type" : {
1531 | "kind" : "NON_NULL",
1532 | "name" : null,
1533 | "ofType" : {
1534 | "kind" : "SCALAR",
1535 | "name" : "String",
1536 | "ofType" : null
1537 | }
1538 | },
1539 | "defaultValue" : null
1540 | }, {
1541 | "name" : "labels",
1542 | "description" : null,
1543 | "type" : {
1544 | "kind" : "LIST",
1545 | "name" : null,
1546 | "ofType" : {
1547 | "kind" : "SCALAR",
1548 | "name" : "String",
1549 | "ofType" : null
1550 | }
1551 | },
1552 | "defaultValue" : null
1553 | }, {
1554 | "name" : "photoAlbumId",
1555 | "description" : null,
1556 | "type" : {
1557 | "kind" : "SCALAR",
1558 | "name" : "ID",
1559 | "ofType" : null
1560 | },
1561 | "defaultValue" : null
1562 | } ],
1563 | "interfaces" : null,
1564 | "enumValues" : null,
1565 | "possibleTypes" : null
1566 | }, {
1567 | "kind" : "INPUT_OBJECT",
1568 | "name" : "ModelPhotoConditionInput",
1569 | "description" : null,
1570 | "fields" : null,
1571 | "inputFields" : [ {
1572 | "name" : "bucket",
1573 | "description" : null,
1574 | "type" : {
1575 | "kind" : "INPUT_OBJECT",
1576 | "name" : "ModelStringInput",
1577 | "ofType" : null
1578 | },
1579 | "defaultValue" : null
1580 | }, {
1581 | "name" : "name",
1582 | "description" : null,
1583 | "type" : {
1584 | "kind" : "INPUT_OBJECT",
1585 | "name" : "ModelStringInput",
1586 | "ofType" : null
1587 | },
1588 | "defaultValue" : null
1589 | }, {
1590 | "name" : "createdAt",
1591 | "description" : null,
1592 | "type" : {
1593 | "kind" : "INPUT_OBJECT",
1594 | "name" : "ModelStringInput",
1595 | "ofType" : null
1596 | },
1597 | "defaultValue" : null
1598 | }, {
1599 | "name" : "labels",
1600 | "description" : null,
1601 | "type" : {
1602 | "kind" : "INPUT_OBJECT",
1603 | "name" : "ModelStringInput",
1604 | "ofType" : null
1605 | },
1606 | "defaultValue" : null
1607 | }, {
1608 | "name" : "and",
1609 | "description" : null,
1610 | "type" : {
1611 | "kind" : "LIST",
1612 | "name" : null,
1613 | "ofType" : {
1614 | "kind" : "INPUT_OBJECT",
1615 | "name" : "ModelPhotoConditionInput",
1616 | "ofType" : null
1617 | }
1618 | },
1619 | "defaultValue" : null
1620 | }, {
1621 | "name" : "or",
1622 | "description" : null,
1623 | "type" : {
1624 | "kind" : "LIST",
1625 | "name" : null,
1626 | "ofType" : {
1627 | "kind" : "INPUT_OBJECT",
1628 | "name" : "ModelPhotoConditionInput",
1629 | "ofType" : null
1630 | }
1631 | },
1632 | "defaultValue" : null
1633 | }, {
1634 | "name" : "not",
1635 | "description" : null,
1636 | "type" : {
1637 | "kind" : "INPUT_OBJECT",
1638 | "name" : "ModelPhotoConditionInput",
1639 | "ofType" : null
1640 | },
1641 | "defaultValue" : null
1642 | } ],
1643 | "interfaces" : null,
1644 | "enumValues" : null,
1645 | "possibleTypes" : null
1646 | }, {
1647 | "kind" : "INPUT_OBJECT",
1648 | "name" : "UpdatePhotoInput",
1649 | "description" : null,
1650 | "fields" : null,
1651 | "inputFields" : [ {
1652 | "name" : "id",
1653 | "description" : null,
1654 | "type" : {
1655 | "kind" : "NON_NULL",
1656 | "name" : null,
1657 | "ofType" : {
1658 | "kind" : "SCALAR",
1659 | "name" : "ID",
1660 | "ofType" : null
1661 | }
1662 | },
1663 | "defaultValue" : null
1664 | }, {
1665 | "name" : "bucket",
1666 | "description" : null,
1667 | "type" : {
1668 | "kind" : "SCALAR",
1669 | "name" : "String",
1670 | "ofType" : null
1671 | },
1672 | "defaultValue" : null
1673 | }, {
1674 | "name" : "name",
1675 | "description" : null,
1676 | "type" : {
1677 | "kind" : "SCALAR",
1678 | "name" : "String",
1679 | "ofType" : null
1680 | },
1681 | "defaultValue" : null
1682 | }, {
1683 | "name" : "createdAt",
1684 | "description" : null,
1685 | "type" : {
1686 | "kind" : "SCALAR",
1687 | "name" : "String",
1688 | "ofType" : null
1689 | },
1690 | "defaultValue" : null
1691 | }, {
1692 | "name" : "labels",
1693 | "description" : null,
1694 | "type" : {
1695 | "kind" : "LIST",
1696 | "name" : null,
1697 | "ofType" : {
1698 | "kind" : "SCALAR",
1699 | "name" : "String",
1700 | "ofType" : null
1701 | }
1702 | },
1703 | "defaultValue" : null
1704 | }, {
1705 | "name" : "photoAlbumId",
1706 | "description" : null,
1707 | "type" : {
1708 | "kind" : "SCALAR",
1709 | "name" : "ID",
1710 | "ofType" : null
1711 | },
1712 | "defaultValue" : null
1713 | } ],
1714 | "interfaces" : null,
1715 | "enumValues" : null,
1716 | "possibleTypes" : null
1717 | }, {
1718 | "kind" : "INPUT_OBJECT",
1719 | "name" : "DeletePhotoInput",
1720 | "description" : null,
1721 | "fields" : null,
1722 | "inputFields" : [ {
1723 | "name" : "id",
1724 | "description" : null,
1725 | "type" : {
1726 | "kind" : "SCALAR",
1727 | "name" : "ID",
1728 | "ofType" : null
1729 | },
1730 | "defaultValue" : null
1731 | } ],
1732 | "interfaces" : null,
1733 | "enumValues" : null,
1734 | "possibleTypes" : null
1735 | }, {
1736 | "kind" : "OBJECT",
1737 | "name" : "Subscription",
1738 | "description" : null,
1739 | "fields" : [ {
1740 | "name" : "onCreateAlbum",
1741 | "description" : null,
1742 | "args" : [ {
1743 | "name" : "owner",
1744 | "description" : null,
1745 | "type" : {
1746 | "kind" : "NON_NULL",
1747 | "name" : null,
1748 | "ofType" : {
1749 | "kind" : "SCALAR",
1750 | "name" : "String",
1751 | "ofType" : null
1752 | }
1753 | },
1754 | "defaultValue" : null
1755 | } ],
1756 | "type" : {
1757 | "kind" : "OBJECT",
1758 | "name" : "Album",
1759 | "ofType" : null
1760 | },
1761 | "isDeprecated" : false,
1762 | "deprecationReason" : null
1763 | }, {
1764 | "name" : "onUpdateAlbum",
1765 | "description" : null,
1766 | "args" : [ {
1767 | "name" : "owner",
1768 | "description" : null,
1769 | "type" : {
1770 | "kind" : "NON_NULL",
1771 | "name" : null,
1772 | "ofType" : {
1773 | "kind" : "SCALAR",
1774 | "name" : "String",
1775 | "ofType" : null
1776 | }
1777 | },
1778 | "defaultValue" : null
1779 | } ],
1780 | "type" : {
1781 | "kind" : "OBJECT",
1782 | "name" : "Album",
1783 | "ofType" : null
1784 | },
1785 | "isDeprecated" : false,
1786 | "deprecationReason" : null
1787 | }, {
1788 | "name" : "onDeleteAlbum",
1789 | "description" : null,
1790 | "args" : [ {
1791 | "name" : "owner",
1792 | "description" : null,
1793 | "type" : {
1794 | "kind" : "NON_NULL",
1795 | "name" : null,
1796 | "ofType" : {
1797 | "kind" : "SCALAR",
1798 | "name" : "String",
1799 | "ofType" : null
1800 | }
1801 | },
1802 | "defaultValue" : null
1803 | } ],
1804 | "type" : {
1805 | "kind" : "OBJECT",
1806 | "name" : "Album",
1807 | "ofType" : null
1808 | },
1809 | "isDeprecated" : false,
1810 | "deprecationReason" : null
1811 | }, {
1812 | "name" : "onCreatePhoto",
1813 | "description" : null,
1814 | "args" : [ {
1815 | "name" : "owner",
1816 | "description" : null,
1817 | "type" : {
1818 | "kind" : "NON_NULL",
1819 | "name" : null,
1820 | "ofType" : {
1821 | "kind" : "SCALAR",
1822 | "name" : "String",
1823 | "ofType" : null
1824 | }
1825 | },
1826 | "defaultValue" : null
1827 | } ],
1828 | "type" : {
1829 | "kind" : "OBJECT",
1830 | "name" : "Photo",
1831 | "ofType" : null
1832 | },
1833 | "isDeprecated" : false,
1834 | "deprecationReason" : null
1835 | }, {
1836 | "name" : "onUpdatePhoto",
1837 | "description" : null,
1838 | "args" : [ {
1839 | "name" : "owner",
1840 | "description" : null,
1841 | "type" : {
1842 | "kind" : "NON_NULL",
1843 | "name" : null,
1844 | "ofType" : {
1845 | "kind" : "SCALAR",
1846 | "name" : "String",
1847 | "ofType" : null
1848 | }
1849 | },
1850 | "defaultValue" : null
1851 | } ],
1852 | "type" : {
1853 | "kind" : "OBJECT",
1854 | "name" : "Photo",
1855 | "ofType" : null
1856 | },
1857 | "isDeprecated" : false,
1858 | "deprecationReason" : null
1859 | }, {
1860 | "name" : "onDeletePhoto",
1861 | "description" : null,
1862 | "args" : [ {
1863 | "name" : "owner",
1864 | "description" : null,
1865 | "type" : {
1866 | "kind" : "NON_NULL",
1867 | "name" : null,
1868 | "ofType" : {
1869 | "kind" : "SCALAR",
1870 | "name" : "String",
1871 | "ofType" : null
1872 | }
1873 | },
1874 | "defaultValue" : null
1875 | } ],
1876 | "type" : {
1877 | "kind" : "OBJECT",
1878 | "name" : "Photo",
1879 | "ofType" : null
1880 | },
1881 | "isDeprecated" : false,
1882 | "deprecationReason" : null
1883 | } ],
1884 | "inputFields" : null,
1885 | "interfaces" : [ ],
1886 | "enumValues" : null,
1887 | "possibleTypes" : null
1888 | }, {
1889 | "kind" : "INPUT_OBJECT",
1890 | "name" : "ModelBooleanInput",
1891 | "description" : null,
1892 | "fields" : null,
1893 | "inputFields" : [ {
1894 | "name" : "ne",
1895 | "description" : null,
1896 | "type" : {
1897 | "kind" : "SCALAR",
1898 | "name" : "Boolean",
1899 | "ofType" : null
1900 | },
1901 | "defaultValue" : null
1902 | }, {
1903 | "name" : "eq",
1904 | "description" : null,
1905 | "type" : {
1906 | "kind" : "SCALAR",
1907 | "name" : "Boolean",
1908 | "ofType" : null
1909 | },
1910 | "defaultValue" : null
1911 | }, {
1912 | "name" : "attributeExists",
1913 | "description" : null,
1914 | "type" : {
1915 | "kind" : "SCALAR",
1916 | "name" : "Boolean",
1917 | "ofType" : null
1918 | },
1919 | "defaultValue" : null
1920 | }, {
1921 | "name" : "attributeType",
1922 | "description" : null,
1923 | "type" : {
1924 | "kind" : "ENUM",
1925 | "name" : "ModelAttributeTypes",
1926 | "ofType" : null
1927 | },
1928 | "defaultValue" : null
1929 | } ],
1930 | "interfaces" : null,
1931 | "enumValues" : null,
1932 | "possibleTypes" : null
1933 | }, {
1934 | "kind" : "INPUT_OBJECT",
1935 | "name" : "ModelFloatInput",
1936 | "description" : null,
1937 | "fields" : null,
1938 | "inputFields" : [ {
1939 | "name" : "ne",
1940 | "description" : null,
1941 | "type" : {
1942 | "kind" : "SCALAR",
1943 | "name" : "Float",
1944 | "ofType" : null
1945 | },
1946 | "defaultValue" : null
1947 | }, {
1948 | "name" : "eq",
1949 | "description" : null,
1950 | "type" : {
1951 | "kind" : "SCALAR",
1952 | "name" : "Float",
1953 | "ofType" : null
1954 | },
1955 | "defaultValue" : null
1956 | }, {
1957 | "name" : "le",
1958 | "description" : null,
1959 | "type" : {
1960 | "kind" : "SCALAR",
1961 | "name" : "Float",
1962 | "ofType" : null
1963 | },
1964 | "defaultValue" : null
1965 | }, {
1966 | "name" : "lt",
1967 | "description" : null,
1968 | "type" : {
1969 | "kind" : "SCALAR",
1970 | "name" : "Float",
1971 | "ofType" : null
1972 | },
1973 | "defaultValue" : null
1974 | }, {
1975 | "name" : "ge",
1976 | "description" : null,
1977 | "type" : {
1978 | "kind" : "SCALAR",
1979 | "name" : "Float",
1980 | "ofType" : null
1981 | },
1982 | "defaultValue" : null
1983 | }, {
1984 | "name" : "gt",
1985 | "description" : null,
1986 | "type" : {
1987 | "kind" : "SCALAR",
1988 | "name" : "Float",
1989 | "ofType" : null
1990 | },
1991 | "defaultValue" : null
1992 | }, {
1993 | "name" : "between",
1994 | "description" : null,
1995 | "type" : {
1996 | "kind" : "LIST",
1997 | "name" : null,
1998 | "ofType" : {
1999 | "kind" : "SCALAR",
2000 | "name" : "Float",
2001 | "ofType" : null
2002 | }
2003 | },
2004 | "defaultValue" : null
2005 | }, {
2006 | "name" : "attributeExists",
2007 | "description" : null,
2008 | "type" : {
2009 | "kind" : "SCALAR",
2010 | "name" : "Boolean",
2011 | "ofType" : null
2012 | },
2013 | "defaultValue" : null
2014 | }, {
2015 | "name" : "attributeType",
2016 | "description" : null,
2017 | "type" : {
2018 | "kind" : "ENUM",
2019 | "name" : "ModelAttributeTypes",
2020 | "ofType" : null
2021 | },
2022 | "defaultValue" : null
2023 | } ],
2024 | "interfaces" : null,
2025 | "enumValues" : null,
2026 | "possibleTypes" : null
2027 | }, {
2028 | "kind" : "SCALAR",
2029 | "name" : "Float",
2030 | "description" : "Built-in Float",
2031 | "fields" : null,
2032 | "inputFields" : null,
2033 | "interfaces" : null,
2034 | "enumValues" : null,
2035 | "possibleTypes" : null
2036 | }, {
2037 | "kind" : "INPUT_OBJECT",
2038 | "name" : "ModelIntInput",
2039 | "description" : null,
2040 | "fields" : null,
2041 | "inputFields" : [ {
2042 | "name" : "ne",
2043 | "description" : null,
2044 | "type" : {
2045 | "kind" : "SCALAR",
2046 | "name" : "Int",
2047 | "ofType" : null
2048 | },
2049 | "defaultValue" : null
2050 | }, {
2051 | "name" : "eq",
2052 | "description" : null,
2053 | "type" : {
2054 | "kind" : "SCALAR",
2055 | "name" : "Int",
2056 | "ofType" : null
2057 | },
2058 | "defaultValue" : null
2059 | }, {
2060 | "name" : "le",
2061 | "description" : null,
2062 | "type" : {
2063 | "kind" : "SCALAR",
2064 | "name" : "Int",
2065 | "ofType" : null
2066 | },
2067 | "defaultValue" : null
2068 | }, {
2069 | "name" : "lt",
2070 | "description" : null,
2071 | "type" : {
2072 | "kind" : "SCALAR",
2073 | "name" : "Int",
2074 | "ofType" : null
2075 | },
2076 | "defaultValue" : null
2077 | }, {
2078 | "name" : "ge",
2079 | "description" : null,
2080 | "type" : {
2081 | "kind" : "SCALAR",
2082 | "name" : "Int",
2083 | "ofType" : null
2084 | },
2085 | "defaultValue" : null
2086 | }, {
2087 | "name" : "gt",
2088 | "description" : null,
2089 | "type" : {
2090 | "kind" : "SCALAR",
2091 | "name" : "Int",
2092 | "ofType" : null
2093 | },
2094 | "defaultValue" : null
2095 | }, {
2096 | "name" : "between",
2097 | "description" : null,
2098 | "type" : {
2099 | "kind" : "LIST",
2100 | "name" : null,
2101 | "ofType" : {
2102 | "kind" : "SCALAR",
2103 | "name" : "Int",
2104 | "ofType" : null
2105 | }
2106 | },
2107 | "defaultValue" : null
2108 | }, {
2109 | "name" : "attributeExists",
2110 | "description" : null,
2111 | "type" : {
2112 | "kind" : "SCALAR",
2113 | "name" : "Boolean",
2114 | "ofType" : null
2115 | },
2116 | "defaultValue" : null
2117 | }, {
2118 | "name" : "attributeType",
2119 | "description" : null,
2120 | "type" : {
2121 | "kind" : "ENUM",
2122 | "name" : "ModelAttributeTypes",
2123 | "ofType" : null
2124 | },
2125 | "defaultValue" : null
2126 | } ],
2127 | "interfaces" : null,
2128 | "enumValues" : null,
2129 | "possibleTypes" : null
2130 | }, {
2131 | "kind" : "OBJECT",
2132 | "name" : "__Schema",
2133 | "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.",
2134 | "fields" : [ {
2135 | "name" : "types",
2136 | "description" : "A list of all types supported by this server.",
2137 | "args" : [ ],
2138 | "type" : {
2139 | "kind" : "NON_NULL",
2140 | "name" : null,
2141 | "ofType" : {
2142 | "kind" : "LIST",
2143 | "name" : null,
2144 | "ofType" : {
2145 | "kind" : "NON_NULL",
2146 | "name" : null,
2147 | "ofType" : {
2148 | "kind" : "OBJECT",
2149 | "name" : "__Type",
2150 | "ofType" : null
2151 | }
2152 | }
2153 | }
2154 | },
2155 | "isDeprecated" : false,
2156 | "deprecationReason" : null
2157 | }, {
2158 | "name" : "queryType",
2159 | "description" : "The type that query operations will be rooted at.",
2160 | "args" : [ ],
2161 | "type" : {
2162 | "kind" : "NON_NULL",
2163 | "name" : null,
2164 | "ofType" : {
2165 | "kind" : "OBJECT",
2166 | "name" : "__Type",
2167 | "ofType" : null
2168 | }
2169 | },
2170 | "isDeprecated" : false,
2171 | "deprecationReason" : null
2172 | }, {
2173 | "name" : "mutationType",
2174 | "description" : "If this server supports mutation, the type that mutation operations will be rooted at.",
2175 | "args" : [ ],
2176 | "type" : {
2177 | "kind" : "OBJECT",
2178 | "name" : "__Type",
2179 | "ofType" : null
2180 | },
2181 | "isDeprecated" : false,
2182 | "deprecationReason" : null
2183 | }, {
2184 | "name" : "directives",
2185 | "description" : "'A list of all directives supported by this server.",
2186 | "args" : [ ],
2187 | "type" : {
2188 | "kind" : "NON_NULL",
2189 | "name" : null,
2190 | "ofType" : {
2191 | "kind" : "LIST",
2192 | "name" : null,
2193 | "ofType" : {
2194 | "kind" : "NON_NULL",
2195 | "name" : null,
2196 | "ofType" : {
2197 | "kind" : "OBJECT",
2198 | "name" : "__Directive",
2199 | "ofType" : null
2200 | }
2201 | }
2202 | }
2203 | },
2204 | "isDeprecated" : false,
2205 | "deprecationReason" : null
2206 | }, {
2207 | "name" : "subscriptionType",
2208 | "description" : "'If this server support subscription, the type that subscription operations will be rooted at.",
2209 | "args" : [ ],
2210 | "type" : {
2211 | "kind" : "OBJECT",
2212 | "name" : "__Type",
2213 | "ofType" : null
2214 | },
2215 | "isDeprecated" : false,
2216 | "deprecationReason" : null
2217 | } ],
2218 | "inputFields" : null,
2219 | "interfaces" : [ ],
2220 | "enumValues" : null,
2221 | "possibleTypes" : null
2222 | }, {
2223 | "kind" : "OBJECT",
2224 | "name" : "__Type",
2225 | "description" : null,
2226 | "fields" : [ {
2227 | "name" : "kind",
2228 | "description" : null,
2229 | "args" : [ ],
2230 | "type" : {
2231 | "kind" : "NON_NULL",
2232 | "name" : null,
2233 | "ofType" : {
2234 | "kind" : "ENUM",
2235 | "name" : "__TypeKind",
2236 | "ofType" : null
2237 | }
2238 | },
2239 | "isDeprecated" : false,
2240 | "deprecationReason" : null
2241 | }, {
2242 | "name" : "name",
2243 | "description" : null,
2244 | "args" : [ ],
2245 | "type" : {
2246 | "kind" : "SCALAR",
2247 | "name" : "String",
2248 | "ofType" : null
2249 | },
2250 | "isDeprecated" : false,
2251 | "deprecationReason" : null
2252 | }, {
2253 | "name" : "description",
2254 | "description" : null,
2255 | "args" : [ ],
2256 | "type" : {
2257 | "kind" : "SCALAR",
2258 | "name" : "String",
2259 | "ofType" : null
2260 | },
2261 | "isDeprecated" : false,
2262 | "deprecationReason" : null
2263 | }, {
2264 | "name" : "fields",
2265 | "description" : null,
2266 | "args" : [ {
2267 | "name" : "includeDeprecated",
2268 | "description" : null,
2269 | "type" : {
2270 | "kind" : "SCALAR",
2271 | "name" : "Boolean",
2272 | "ofType" : null
2273 | },
2274 | "defaultValue" : "false"
2275 | } ],
2276 | "type" : {
2277 | "kind" : "LIST",
2278 | "name" : null,
2279 | "ofType" : {
2280 | "kind" : "NON_NULL",
2281 | "name" : null,
2282 | "ofType" : {
2283 | "kind" : "OBJECT",
2284 | "name" : "__Field",
2285 | "ofType" : null
2286 | }
2287 | }
2288 | },
2289 | "isDeprecated" : false,
2290 | "deprecationReason" : null
2291 | }, {
2292 | "name" : "interfaces",
2293 | "description" : null,
2294 | "args" : [ ],
2295 | "type" : {
2296 | "kind" : "LIST",
2297 | "name" : null,
2298 | "ofType" : {
2299 | "kind" : "NON_NULL",
2300 | "name" : null,
2301 | "ofType" : {
2302 | "kind" : "OBJECT",
2303 | "name" : "__Type",
2304 | "ofType" : null
2305 | }
2306 | }
2307 | },
2308 | "isDeprecated" : false,
2309 | "deprecationReason" : null
2310 | }, {
2311 | "name" : "possibleTypes",
2312 | "description" : null,
2313 | "args" : [ ],
2314 | "type" : {
2315 | "kind" : "LIST",
2316 | "name" : null,
2317 | "ofType" : {
2318 | "kind" : "NON_NULL",
2319 | "name" : null,
2320 | "ofType" : {
2321 | "kind" : "OBJECT",
2322 | "name" : "__Type",
2323 | "ofType" : null
2324 | }
2325 | }
2326 | },
2327 | "isDeprecated" : false,
2328 | "deprecationReason" : null
2329 | }, {
2330 | "name" : "enumValues",
2331 | "description" : null,
2332 | "args" : [ {
2333 | "name" : "includeDeprecated",
2334 | "description" : null,
2335 | "type" : {
2336 | "kind" : "SCALAR",
2337 | "name" : "Boolean",
2338 | "ofType" : null
2339 | },
2340 | "defaultValue" : "false"
2341 | } ],
2342 | "type" : {
2343 | "kind" : "LIST",
2344 | "name" : null,
2345 | "ofType" : {
2346 | "kind" : "NON_NULL",
2347 | "name" : null,
2348 | "ofType" : {
2349 | "kind" : "OBJECT",
2350 | "name" : "__EnumValue",
2351 | "ofType" : null
2352 | }
2353 | }
2354 | },
2355 | "isDeprecated" : false,
2356 | "deprecationReason" : null
2357 | }, {
2358 | "name" : "inputFields",
2359 | "description" : null,
2360 | "args" : [ ],
2361 | "type" : {
2362 | "kind" : "LIST",
2363 | "name" : null,
2364 | "ofType" : {
2365 | "kind" : "NON_NULL",
2366 | "name" : null,
2367 | "ofType" : {
2368 | "kind" : "OBJECT",
2369 | "name" : "__InputValue",
2370 | "ofType" : null
2371 | }
2372 | }
2373 | },
2374 | "isDeprecated" : false,
2375 | "deprecationReason" : null
2376 | }, {
2377 | "name" : "ofType",
2378 | "description" : null,
2379 | "args" : [ ],
2380 | "type" : {
2381 | "kind" : "OBJECT",
2382 | "name" : "__Type",
2383 | "ofType" : null
2384 | },
2385 | "isDeprecated" : false,
2386 | "deprecationReason" : null
2387 | } ],
2388 | "inputFields" : null,
2389 | "interfaces" : [ ],
2390 | "enumValues" : null,
2391 | "possibleTypes" : null
2392 | }, {
2393 | "kind" : "ENUM",
2394 | "name" : "__TypeKind",
2395 | "description" : "An enum describing what kind of type a given __Type is",
2396 | "fields" : null,
2397 | "inputFields" : null,
2398 | "interfaces" : null,
2399 | "enumValues" : [ {
2400 | "name" : "SCALAR",
2401 | "description" : "Indicates this type is a scalar.",
2402 | "isDeprecated" : false,
2403 | "deprecationReason" : null
2404 | }, {
2405 | "name" : "OBJECT",
2406 | "description" : "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
2407 | "isDeprecated" : false,
2408 | "deprecationReason" : null
2409 | }, {
2410 | "name" : "INTERFACE",
2411 | "description" : "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
2412 | "isDeprecated" : false,
2413 | "deprecationReason" : null
2414 | }, {
2415 | "name" : "UNION",
2416 | "description" : "Indicates this type is a union. `possibleTypes` is a valid field.",
2417 | "isDeprecated" : false,
2418 | "deprecationReason" : null
2419 | }, {
2420 | "name" : "ENUM",
2421 | "description" : "Indicates this type is an enum. `enumValues` is a valid field.",
2422 | "isDeprecated" : false,
2423 | "deprecationReason" : null
2424 | }, {
2425 | "name" : "INPUT_OBJECT",
2426 | "description" : "Indicates this type is an input object. `inputFields` is a valid field.",
2427 | "isDeprecated" : false,
2428 | "deprecationReason" : null
2429 | }, {
2430 | "name" : "LIST",
2431 | "description" : "Indicates this type is a list. `ofType` is a valid field.",
2432 | "isDeprecated" : false,
2433 | "deprecationReason" : null
2434 | }, {
2435 | "name" : "NON_NULL",
2436 | "description" : "Indicates this type is a non-null. `ofType` is a valid field.",
2437 | "isDeprecated" : false,
2438 | "deprecationReason" : null
2439 | } ],
2440 | "possibleTypes" : null
2441 | }, {
2442 | "kind" : "OBJECT",
2443 | "name" : "__Field",
2444 | "description" : null,
2445 | "fields" : [ {
2446 | "name" : "name",
2447 | "description" : null,
2448 | "args" : [ ],
2449 | "type" : {
2450 | "kind" : "NON_NULL",
2451 | "name" : null,
2452 | "ofType" : {
2453 | "kind" : "SCALAR",
2454 | "name" : "String",
2455 | "ofType" : null
2456 | }
2457 | },
2458 | "isDeprecated" : false,
2459 | "deprecationReason" : null
2460 | }, {
2461 | "name" : "description",
2462 | "description" : null,
2463 | "args" : [ ],
2464 | "type" : {
2465 | "kind" : "SCALAR",
2466 | "name" : "String",
2467 | "ofType" : null
2468 | },
2469 | "isDeprecated" : false,
2470 | "deprecationReason" : null
2471 | }, {
2472 | "name" : "args",
2473 | "description" : null,
2474 | "args" : [ ],
2475 | "type" : {
2476 | "kind" : "NON_NULL",
2477 | "name" : null,
2478 | "ofType" : {
2479 | "kind" : "LIST",
2480 | "name" : null,
2481 | "ofType" : {
2482 | "kind" : "NON_NULL",
2483 | "name" : null,
2484 | "ofType" : {
2485 | "kind" : "OBJECT",
2486 | "name" : "__InputValue",
2487 | "ofType" : null
2488 | }
2489 | }
2490 | }
2491 | },
2492 | "isDeprecated" : false,
2493 | "deprecationReason" : null
2494 | }, {
2495 | "name" : "type",
2496 | "description" : null,
2497 | "args" : [ ],
2498 | "type" : {
2499 | "kind" : "NON_NULL",
2500 | "name" : null,
2501 | "ofType" : {
2502 | "kind" : "OBJECT",
2503 | "name" : "__Type",
2504 | "ofType" : null
2505 | }
2506 | },
2507 | "isDeprecated" : false,
2508 | "deprecationReason" : null
2509 | }, {
2510 | "name" : "isDeprecated",
2511 | "description" : null,
2512 | "args" : [ ],
2513 | "type" : {
2514 | "kind" : "NON_NULL",
2515 | "name" : null,
2516 | "ofType" : {
2517 | "kind" : "SCALAR",
2518 | "name" : "Boolean",
2519 | "ofType" : null
2520 | }
2521 | },
2522 | "isDeprecated" : false,
2523 | "deprecationReason" : null
2524 | }, {
2525 | "name" : "deprecationReason",
2526 | "description" : null,
2527 | "args" : [ ],
2528 | "type" : {
2529 | "kind" : "SCALAR",
2530 | "name" : "String",
2531 | "ofType" : null
2532 | },
2533 | "isDeprecated" : false,
2534 | "deprecationReason" : null
2535 | } ],
2536 | "inputFields" : null,
2537 | "interfaces" : [ ],
2538 | "enumValues" : null,
2539 | "possibleTypes" : null
2540 | }, {
2541 | "kind" : "OBJECT",
2542 | "name" : "__InputValue",
2543 | "description" : null,
2544 | "fields" : [ {
2545 | "name" : "name",
2546 | "description" : null,
2547 | "args" : [ ],
2548 | "type" : {
2549 | "kind" : "NON_NULL",
2550 | "name" : null,
2551 | "ofType" : {
2552 | "kind" : "SCALAR",
2553 | "name" : "String",
2554 | "ofType" : null
2555 | }
2556 | },
2557 | "isDeprecated" : false,
2558 | "deprecationReason" : null
2559 | }, {
2560 | "name" : "description",
2561 | "description" : null,
2562 | "args" : [ ],
2563 | "type" : {
2564 | "kind" : "SCALAR",
2565 | "name" : "String",
2566 | "ofType" : null
2567 | },
2568 | "isDeprecated" : false,
2569 | "deprecationReason" : null
2570 | }, {
2571 | "name" : "type",
2572 | "description" : null,
2573 | "args" : [ ],
2574 | "type" : {
2575 | "kind" : "NON_NULL",
2576 | "name" : null,
2577 | "ofType" : {
2578 | "kind" : "OBJECT",
2579 | "name" : "__Type",
2580 | "ofType" : null
2581 | }
2582 | },
2583 | "isDeprecated" : false,
2584 | "deprecationReason" : null
2585 | }, {
2586 | "name" : "defaultValue",
2587 | "description" : null,
2588 | "args" : [ ],
2589 | "type" : {
2590 | "kind" : "SCALAR",
2591 | "name" : "String",
2592 | "ofType" : null
2593 | },
2594 | "isDeprecated" : false,
2595 | "deprecationReason" : null
2596 | } ],
2597 | "inputFields" : null,
2598 | "interfaces" : [ ],
2599 | "enumValues" : null,
2600 | "possibleTypes" : null
2601 | }, {
2602 | "kind" : "OBJECT",
2603 | "name" : "__EnumValue",
2604 | "description" : null,
2605 | "fields" : [ {
2606 | "name" : "name",
2607 | "description" : null,
2608 | "args" : [ ],
2609 | "type" : {
2610 | "kind" : "NON_NULL",
2611 | "name" : null,
2612 | "ofType" : {
2613 | "kind" : "SCALAR",
2614 | "name" : "String",
2615 | "ofType" : null
2616 | }
2617 | },
2618 | "isDeprecated" : false,
2619 | "deprecationReason" : null
2620 | }, {
2621 | "name" : "description",
2622 | "description" : null,
2623 | "args" : [ ],
2624 | "type" : {
2625 | "kind" : "SCALAR",
2626 | "name" : "String",
2627 | "ofType" : null
2628 | },
2629 | "isDeprecated" : false,
2630 | "deprecationReason" : null
2631 | }, {
2632 | "name" : "isDeprecated",
2633 | "description" : null,
2634 | "args" : [ ],
2635 | "type" : {
2636 | "kind" : "NON_NULL",
2637 | "name" : null,
2638 | "ofType" : {
2639 | "kind" : "SCALAR",
2640 | "name" : "Boolean",
2641 | "ofType" : null
2642 | }
2643 | },
2644 | "isDeprecated" : false,
2645 | "deprecationReason" : null
2646 | }, {
2647 | "name" : "deprecationReason",
2648 | "description" : null,
2649 | "args" : [ ],
2650 | "type" : {
2651 | "kind" : "SCALAR",
2652 | "name" : "String",
2653 | "ofType" : null
2654 | },
2655 | "isDeprecated" : false,
2656 | "deprecationReason" : null
2657 | } ],
2658 | "inputFields" : null,
2659 | "interfaces" : [ ],
2660 | "enumValues" : null,
2661 | "possibleTypes" : null
2662 | }, {
2663 | "kind" : "OBJECT",
2664 | "name" : "__Directive",
2665 | "description" : null,
2666 | "fields" : [ {
2667 | "name" : "name",
2668 | "description" : null,
2669 | "args" : [ ],
2670 | "type" : {
2671 | "kind" : "SCALAR",
2672 | "name" : "String",
2673 | "ofType" : null
2674 | },
2675 | "isDeprecated" : false,
2676 | "deprecationReason" : null
2677 | }, {
2678 | "name" : "description",
2679 | "description" : null,
2680 | "args" : [ ],
2681 | "type" : {
2682 | "kind" : "SCALAR",
2683 | "name" : "String",
2684 | "ofType" : null
2685 | },
2686 | "isDeprecated" : false,
2687 | "deprecationReason" : null
2688 | }, {
2689 | "name" : "locations",
2690 | "description" : null,
2691 | "args" : [ ],
2692 | "type" : {
2693 | "kind" : "LIST",
2694 | "name" : null,
2695 | "ofType" : {
2696 | "kind" : "NON_NULL",
2697 | "name" : null,
2698 | "ofType" : {
2699 | "kind" : "ENUM",
2700 | "name" : "__DirectiveLocation",
2701 | "ofType" : null
2702 | }
2703 | }
2704 | },
2705 | "isDeprecated" : false,
2706 | "deprecationReason" : null
2707 | }, {
2708 | "name" : "args",
2709 | "description" : null,
2710 | "args" : [ ],
2711 | "type" : {
2712 | "kind" : "NON_NULL",
2713 | "name" : null,
2714 | "ofType" : {
2715 | "kind" : "LIST",
2716 | "name" : null,
2717 | "ofType" : {
2718 | "kind" : "NON_NULL",
2719 | "name" : null,
2720 | "ofType" : {
2721 | "kind" : "OBJECT",
2722 | "name" : "__InputValue",
2723 | "ofType" : null
2724 | }
2725 | }
2726 | }
2727 | },
2728 | "isDeprecated" : false,
2729 | "deprecationReason" : null
2730 | }, {
2731 | "name" : "onOperation",
2732 | "description" : null,
2733 | "args" : [ ],
2734 | "type" : {
2735 | "kind" : "SCALAR",
2736 | "name" : "Boolean",
2737 | "ofType" : null
2738 | },
2739 | "isDeprecated" : true,
2740 | "deprecationReason" : "Use `locations`."
2741 | }, {
2742 | "name" : "onFragment",
2743 | "description" : null,
2744 | "args" : [ ],
2745 | "type" : {
2746 | "kind" : "SCALAR",
2747 | "name" : "Boolean",
2748 | "ofType" : null
2749 | },
2750 | "isDeprecated" : true,
2751 | "deprecationReason" : "Use `locations`."
2752 | }, {
2753 | "name" : "onField",
2754 | "description" : null,
2755 | "args" : [ ],
2756 | "type" : {
2757 | "kind" : "SCALAR",
2758 | "name" : "Boolean",
2759 | "ofType" : null
2760 | },
2761 | "isDeprecated" : true,
2762 | "deprecationReason" : "Use `locations`."
2763 | } ],
2764 | "inputFields" : null,
2765 | "interfaces" : [ ],
2766 | "enumValues" : null,
2767 | "possibleTypes" : null
2768 | }, {
2769 | "kind" : "ENUM",
2770 | "name" : "__DirectiveLocation",
2771 | "description" : "An enum describing valid locations where a directive can be placed",
2772 | "fields" : null,
2773 | "inputFields" : null,
2774 | "interfaces" : null,
2775 | "enumValues" : [ {
2776 | "name" : "QUERY",
2777 | "description" : "Indicates the directive is valid on queries.",
2778 | "isDeprecated" : false,
2779 | "deprecationReason" : null
2780 | }, {
2781 | "name" : "MUTATION",
2782 | "description" : "Indicates the directive is valid on mutations.",
2783 | "isDeprecated" : false,
2784 | "deprecationReason" : null
2785 | }, {
2786 | "name" : "FIELD",
2787 | "description" : "Indicates the directive is valid on fields.",
2788 | "isDeprecated" : false,
2789 | "deprecationReason" : null
2790 | }, {
2791 | "name" : "FRAGMENT_DEFINITION",
2792 | "description" : "Indicates the directive is valid on fragment definitions.",
2793 | "isDeprecated" : false,
2794 | "deprecationReason" : null
2795 | }, {
2796 | "name" : "FRAGMENT_SPREAD",
2797 | "description" : "Indicates the directive is valid on fragment spreads.",
2798 | "isDeprecated" : false,
2799 | "deprecationReason" : null
2800 | }, {
2801 | "name" : "INLINE_FRAGMENT",
2802 | "description" : "Indicates the directive is valid on inline fragments.",
2803 | "isDeprecated" : false,
2804 | "deprecationReason" : null
2805 | }, {
2806 | "name" : "SCHEMA",
2807 | "description" : "Indicates the directive is valid on a schema SDL definition.",
2808 | "isDeprecated" : false,
2809 | "deprecationReason" : null
2810 | }, {
2811 | "name" : "SCALAR",
2812 | "description" : "Indicates the directive is valid on a scalar SDL definition.",
2813 | "isDeprecated" : false,
2814 | "deprecationReason" : null
2815 | }, {
2816 | "name" : "OBJECT",
2817 | "description" : "Indicates the directive is valid on an object SDL definition.",
2818 | "isDeprecated" : false,
2819 | "deprecationReason" : null
2820 | }, {
2821 | "name" : "FIELD_DEFINITION",
2822 | "description" : "Indicates the directive is valid on a field SDL definition.",
2823 | "isDeprecated" : false,
2824 | "deprecationReason" : null
2825 | }, {
2826 | "name" : "ARGUMENT_DEFINITION",
2827 | "description" : "Indicates the directive is valid on a field argument SDL definition.",
2828 | "isDeprecated" : false,
2829 | "deprecationReason" : null
2830 | }, {
2831 | "name" : "INTERFACE",
2832 | "description" : "Indicates the directive is valid on an interface SDL definition.",
2833 | "isDeprecated" : false,
2834 | "deprecationReason" : null
2835 | }, {
2836 | "name" : "UNION",
2837 | "description" : "Indicates the directive is valid on an union SDL definition.",
2838 | "isDeprecated" : false,
2839 | "deprecationReason" : null
2840 | }, {
2841 | "name" : "ENUM",
2842 | "description" : "Indicates the directive is valid on an enum SDL definition.",
2843 | "isDeprecated" : false,
2844 | "deprecationReason" : null
2845 | }, {
2846 | "name" : "ENUM_VALUE",
2847 | "description" : "Indicates the directive is valid on an enum value SDL definition.",
2848 | "isDeprecated" : false,
2849 | "deprecationReason" : null
2850 | }, {
2851 | "name" : "INPUT_OBJECT",
2852 | "description" : "Indicates the directive is valid on an input object SDL definition.",
2853 | "isDeprecated" : false,
2854 | "deprecationReason" : null
2855 | }, {
2856 | "name" : "INPUT_FIELD_DEFINITION",
2857 | "description" : "Indicates the directive is valid on an input object field SDL definition.",
2858 | "isDeprecated" : false,
2859 | "deprecationReason" : null
2860 | } ],
2861 | "possibleTypes" : null
2862 | } ],
2863 | "directives" : [ {
2864 | "name" : "include",
2865 | "description" : "Directs the executor to include this field or fragment only when the `if` argument is true",
2866 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ],
2867 | "args" : [ {
2868 | "name" : "if",
2869 | "description" : "Included when true.",
2870 | "type" : {
2871 | "kind" : "NON_NULL",
2872 | "name" : null,
2873 | "ofType" : {
2874 | "kind" : "SCALAR",
2875 | "name" : "Boolean",
2876 | "ofType" : null
2877 | }
2878 | },
2879 | "defaultValue" : null
2880 | } ],
2881 | "onOperation" : false,
2882 | "onFragment" : true,
2883 | "onField" : true
2884 | }, {
2885 | "name" : "skip",
2886 | "description" : "Directs the executor to skip this field or fragment when the `if`'argument is true.",
2887 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ],
2888 | "args" : [ {
2889 | "name" : "if",
2890 | "description" : "Skipped when true.",
2891 | "type" : {
2892 | "kind" : "NON_NULL",
2893 | "name" : null,
2894 | "ofType" : {
2895 | "kind" : "SCALAR",
2896 | "name" : "Boolean",
2897 | "ofType" : null
2898 | }
2899 | },
2900 | "defaultValue" : null
2901 | } ],
2902 | "onOperation" : false,
2903 | "onFragment" : true,
2904 | "onField" : true
2905 | }, {
2906 | "name" : "defer",
2907 | "description" : "This directive allows results to be deferred during execution",
2908 | "locations" : [ "FIELD" ],
2909 | "args" : [ ],
2910 | "onOperation" : false,
2911 | "onFragment" : false,
2912 | "onField" : true
2913 | }, {
2914 | "name" : "aws_cognito_user_pools",
2915 | "description" : "Tells the service this field/object has access authorized by a Cognito User Pools token.",
2916 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ],
2917 | "args" : [ {
2918 | "name" : "cognito_groups",
2919 | "description" : "List of cognito user pool groups which have access on this field",
2920 | "type" : {
2921 | "kind" : "LIST",
2922 | "name" : null,
2923 | "ofType" : {
2924 | "kind" : "SCALAR",
2925 | "name" : "String",
2926 | "ofType" : null
2927 | }
2928 | },
2929 | "defaultValue" : null
2930 | } ],
2931 | "onOperation" : false,
2932 | "onFragment" : false,
2933 | "onField" : false
2934 | }, {
2935 | "name" : "aws_oidc",
2936 | "description" : "Tells the service this field/object has access authorized by an OIDC token.",
2937 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ],
2938 | "args" : [ ],
2939 | "onOperation" : false,
2940 | "onFragment" : false,
2941 | "onField" : false
2942 | }, {
2943 | "name" : "aws_api_key",
2944 | "description" : "Tells the service this field/object has access authorized by an API key.",
2945 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ],
2946 | "args" : [ ],
2947 | "onOperation" : false,
2948 | "onFragment" : false,
2949 | "onField" : false
2950 | }, {
2951 | "name" : "aws_auth",
2952 | "description" : "Directs the schema to enforce authorization on a field",
2953 | "locations" : [ "FIELD_DEFINITION" ],
2954 | "args" : [ {
2955 | "name" : "cognito_groups",
2956 | "description" : "List of cognito user pool groups which have access on this field",
2957 | "type" : {
2958 | "kind" : "LIST",
2959 | "name" : null,
2960 | "ofType" : {
2961 | "kind" : "SCALAR",
2962 | "name" : "String",
2963 | "ofType" : null
2964 | }
2965 | },
2966 | "defaultValue" : null
2967 | } ],
2968 | "onOperation" : false,
2969 | "onFragment" : false,
2970 | "onField" : false
2971 | }, {
2972 | "name" : "aws_publish",
2973 | "description" : "Tells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.",
2974 | "locations" : [ "FIELD_DEFINITION" ],
2975 | "args" : [ {
2976 | "name" : "subscriptions",
2977 | "description" : "List of subscriptions which will be published to when this mutation is called.",
2978 | "type" : {
2979 | "kind" : "LIST",
2980 | "name" : null,
2981 | "ofType" : {
2982 | "kind" : "SCALAR",
2983 | "name" : "String",
2984 | "ofType" : null
2985 | }
2986 | },
2987 | "defaultValue" : null
2988 | } ],
2989 | "onOperation" : false,
2990 | "onFragment" : false,
2991 | "onField" : false
2992 | }, {
2993 | "name" : "aws_iam",
2994 | "description" : "Tells the service this field/object has access authorized by sigv4 signing.",
2995 | "locations" : [ "OBJECT", "FIELD_DEFINITION" ],
2996 | "args" : [ ],
2997 | "onOperation" : false,
2998 | "onFragment" : false,
2999 | "onField" : false
3000 | }, {
3001 | "name" : "aws_subscribe",
3002 | "description" : "Tells the service which mutation triggers this subscription.",
3003 | "locations" : [ "FIELD_DEFINITION" ],
3004 | "args" : [ {
3005 | "name" : "mutations",
3006 | "description" : "List of mutations which will trigger this subscription when they are called.",
3007 | "type" : {
3008 | "kind" : "LIST",
3009 | "name" : null,
3010 | "ofType" : {
3011 | "kind" : "SCALAR",
3012 | "name" : "String",
3013 | "ofType" : null
3014 | }
3015 | },
3016 | "defaultValue" : null
3017 | } ],
3018 | "onOperation" : false,
3019 | "onFragment" : false,
3020 | "onField" : false
3021 | }, {
3022 | "name" : "deprecated",
3023 | "description" : null,
3024 | "locations" : [ "FIELD_DEFINITION", "ENUM_VALUE" ],
3025 | "args" : [ {
3026 | "name" : "reason",
3027 | "description" : null,
3028 | "type" : {
3029 | "kind" : "SCALAR",
3030 | "name" : "String",
3031 | "ofType" : null
3032 | },
3033 | "defaultValue" : "\"No longer supported\""
3034 | } ],
3035 | "onOperation" : false,
3036 | "onFragment" : false,
3037 | "onField" : false
3038 | } ]
3039 | }
3040 | }
3041 | }
--------------------------------------------------------------------------------
/src/graphql/subscriptions.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const onCreateAlbum = `subscription OnCreateAlbum($owner: String!) {
5 | onCreateAlbum(owner: $owner) {
6 | id
7 | name
8 | createdAt
9 | photos {
10 | items {
11 | id
12 | bucket
13 | name
14 | createdAt
15 | labels
16 | owner
17 | }
18 | nextToken
19 | }
20 | owner
21 | }
22 | }
23 | `;
24 | export const onUpdateAlbum = `subscription OnUpdateAlbum($owner: String!) {
25 | onUpdateAlbum(owner: $owner) {
26 | id
27 | name
28 | createdAt
29 | photos {
30 | items {
31 | id
32 | bucket
33 | name
34 | createdAt
35 | labels
36 | owner
37 | }
38 | nextToken
39 | }
40 | owner
41 | }
42 | }
43 | `;
44 | export const onDeleteAlbum = `subscription OnDeleteAlbum($owner: String!) {
45 | onDeleteAlbum(owner: $owner) {
46 | id
47 | name
48 | createdAt
49 | photos {
50 | items {
51 | id
52 | bucket
53 | name
54 | createdAt
55 | labels
56 | owner
57 | }
58 | nextToken
59 | }
60 | owner
61 | }
62 | }
63 | `;
64 | export const onCreatePhoto = `subscription OnCreatePhoto($owner: String!) {
65 | onCreatePhoto(owner: $owner) {
66 | id
67 | album {
68 | id
69 | name
70 | createdAt
71 | photos {
72 | nextToken
73 | }
74 | owner
75 | }
76 | bucket
77 | name
78 | createdAt
79 | labels
80 | owner
81 | }
82 | }
83 | `;
84 | export const onUpdatePhoto = `subscription OnUpdatePhoto($owner: String!) {
85 | onUpdatePhoto(owner: $owner) {
86 | id
87 | album {
88 | id
89 | name
90 | createdAt
91 | photos {
92 | nextToken
93 | }
94 | owner
95 | }
96 | bucket
97 | name
98 | createdAt
99 | labels
100 | owner
101 | }
102 | }
103 | `;
104 | export const onDeletePhoto = `subscription OnDeletePhoto($owner: String!) {
105 | onDeletePhoto(owner: $owner) {
106 | id
107 | album {
108 | id
109 | name
110 | createdAt
111 | photos {
112 | nextToken
113 | }
114 | owner
115 | }
116 | bucket
117 | name
118 | createdAt
119 | labels
120 | owner
121 | }
122 | }
123 | `;
124 |
--------------------------------------------------------------------------------
/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 | ReactDOM.render( , document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/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.0/8 are 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 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------