Amplify Studioでフォトストレージを作ってみよう!

こんにちは、高橋です!

今回は、認証・ストレージの機能を使って、フォトストレージアプリを作っていこうと思います!

前提

AWS Amplify StudioでfigmaからReactコンポーネントを作ってみた」を読んでいることを前提として進めていきます。Amplify Studioを使ってReactコンポーネントを作る手順が丁寧に書いてあるので、本記事でわからないところがあったらこちらをご覧ください。

以下の環境で動作を確認できています。

ライブラリバージョン
@aws-amplify/cli7.6.9
@aws-amplify/ui-react2.1.8

プロジェクトの作成

それでは、プロジェクトを作成していきましょう!

まず、以下のコマンドでReactのプロジェクトを作成します。

npx create-react-app photo-storage --template typescript

次に、以下のコマンドを使って、プロジェクトを動かしてみましょう。

cd photo-storage
npm start

はい、正しく動きましたね。

次に、必要なライブラリをインストールしましょう。

認証やFigmaで作成したコンポーネントを使う時に必要になってきます。

npm install @aws-amplify/ui-react

次は、ReactプロジェクトとAmplify Studioを繋ぎこみましょう。

まず、「PhotoStorage」という名前でAmplify Studioをデプロイします。
デプロイ方法は、こちらのブログで詳しく説明しているので、分からない場合は見てください。

Amplify Studioを起動したら、右上にある「Local setup instructions」をクリックすると、以下のコマンドを確認できると思います。そのコマンドをReactプロジェクトのルートディレクトリで実行しましょう。

amplify pull --appId *********** --envName staging

ログインを求められたり、いろいろ質問されたりするので適当に答えていきます。

1番最後に「Do you plan on modifying this backend?」と聞かれるので、「Yes」と答えてください。
「No」にしてしまうと、あとから認証機能などを追加しても、反映されなくなるので気を付けてください。

? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm run build
? Start Command: npm start
? Do you plan on modifying this backend? Yes

これで、ReactアプリとAmplify Studioを繋ぎこむことができました。

認証の追加

ReactアプリとAmplify Studioを繋ぎこむことができたので、認証機能を追加していきましょう。

認証については、小倉くんのブログ「Amplify Studioでログイン認証機能を実装してみる」で詳しく説明されているので、興味がある方は是非見てください。

まずは、サイドメニューから「Authentication」を選択します。

「Configure login」では、ログイン方法や多要素認証などの設定をすることができます。

今回は、メールアドレスにしますが、他にも電話番号・FaceBook・Google・Amazon・Appleでログインが可能です。

次に、「Configure sign up」でサインアップ時に登録させる内容やパスワードの強度を設定します。

試しに「Add attribute」を押して「Name」を追加してみましょう。これで、サインアップ時に名前も聞かれるようになります。

パスワードの強度は、初期状態のままにしておきます。

ここらへんの設定は、自由にやってもらっても問題ないのでいろいろ試してみてください!

設定が終わったら、一番下にある「Deployボタン」を押して認証機能をデプロイしましょう。

Reactプロジェクトに移ります。

以下のコマンドで、先ほどAmplify Studioで変更した内容をReactプロジェクトに反映させましょう!

amplify pull

コマンドを実行すると、変更前と変更後の状態を確認することができます。

変更後に「Auth」が追加されているので、認証機能がReactプロジェクトに追加されていることが分かります。

Pre-pull status:

    Current Environment: staging
    
┌──────────┬───────────────┬───────────┬─────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin │
└──────────┴───────────────┴───────────┴─────────────────┘

✔ Successfully pulled backend environment staging from the cloud.

Post-pull status:

    Current Environment: staging
    
┌──────────┬───────────────┬───────────┬───────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin   │
├──────────┼───────────────┼───────────┼───────────────────┤
│ Auth     │ PhotoStorage  │ No Change │ awscloudformation │
└──────────┴───────────────┴───────────┴───────────────────┘

「App.tsx」と「index.tsx」を以下のように変更します。

import React from 'react';
import logo from './logo.svg';
import './App.css';

import { withAuthenticator } from '@aws-amplify/ui-react';

function App() {
  return (
--------------------  省略  --------------------
  );
}

export default withAuthenticator(App);
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import Amplify from "aws-amplify"
import "@aws-amplify/ui-react/styles.css"
import { AmplifyProvider } from "@aws-amplify/ui-react"
import awsconfig from "./aws-exports"
Amplify.configure(awsconfig)

ReactDOM.render(
  <React.StrictMode>
    <AmplifyProvider>
      <App />
    </AmplifyProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

変更したら、プロジェクトを実行して確認してみましょう。

サインインとサインアップができるようになりました。

アカウントを作ってログインしてみましょう。サインアップ・サインインできることが確認できると思います!

先ほど、名前を追加で入力させるようにしたので「Name」の入力欄が増えていますね。

これでログイン機能をReactプロジェクトに追加することができました。

ちなみに、サインアップ時に確認コードが送られてくるのですが、「Forgot password message」で設定したメッセージで送られてきました。

なんでだろう、パスワードを忘れた際に、送るメールのはずなのに…。仕様なのか、それともバグなのか…。

ストレージの追加

続いては、写真を保存するためのストレージを追加していきたいと思います。

Amplify Studioに戻り、サイドメニューから「Storage」を選択します。

ここでは、ストレージの権限を設定することができます。

Photo Storageを作るので、登録ユーザーだけがストレージを操作できるようにしなければいけません。

そのため、「Signed-in users」のUpload・View・Deleteにチェックを入れます。

チェックを入れたら「Create bucket」をクリックして、ストレージを作成します。

デプロイが完了したらサイドメニューから「File browser」を押してみましょう。

先ほど作成したストレージが表示されていますね。

「private」「protected」「public」とすでにフォルダーがありますね。これらのフォルダはそれぞれ以下のような役割があるようです。

private
privateフォルダにユーザーごとのフォルダが作成されるようです。このフォルダ内にあるファイルは、ファイルの所有者しかアクセス・編集ができないようです。
今回は、所有者だけがファイルにアクセス・編集できるようにしたいので、こちらのフォルダに格納していこうと思います。

protected
protectedフォルダにユーザーごとのフォルダが作成されるようです。このフォルダ内にあるファイルは、他のユーザーからでもアクセスすることができます。ただし、編集は所有者しかできないです。
ファイルなどを他のユーザーに共有するときは、protectedフォルダにファイルを移動させる必要がありそうですね。

【public】
publicフォルダ内にあるファイルは、すべてのユーザーが閲覧・編集することができます。
「share point」などの複数の人が編集できるストレージを作るときに使えますね。

それでは、Reactアプリにストレージを追加していきます。

バックエンドに変更が入ったので、以下のコマンドでpullしてきましょう。

amplify pull

ストレージが追加されているのがわかりますね。

Pre-pull status:

    Current Environment: staging
    
┌──────────┬───────────────┬───────────┬───────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin   │
├──────────┼───────────────┼───────────┼───────────────────┤
│ Auth     │ PhotoStorage  │ No Change │ awscloudformation │
└──────────┴───────────────┴───────────┴───────────────────┘

⠸ Fetching updates to backend environment: staging from the cloud.⠋ Build✔ Successfully pulled backend environment staging from the cloud.

Post-pull status:

    Current Environment: staging
    
┌──────────┬─────────────────────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name                       │ Operation │ Provider plugin   │
├──────────┼─────────────────────────────────────┼───────────┼───────────────────┤
│ Auth     │ PhotoStorage                        │ No Change │ awscloudformation │
├──────────┼─────────────────────────────────────┼───────────┼───────────────────┤
│ Storage  │ s3photostoragestorage************** │ No Change │ awscloudformation │
└──────────┴─────────────────────────────────────┴───────────┴───────────────────┘

コンポーネントの作成

それでは、Figmaでコンポーネントを作っていきましょう!

できたものがこちらになります。

次に、FigmaとAmplify Studioを連携させます。

右上の「Share」を押して、リンクをコピーします。
サイドメニューから「UI Library」を選択したら、Figmaのリンクを貼り付けます。

すると、コンポーネントが読み込まれるので、右上の「Accept all」を押して、すべて読み込みます。

Figmaで作成した「AddButton」と「ImageCard」を読み込むことができました。

「ImageCard」のプロパティーを設定したいので「ImageCard」を選択して「Configure」を押します。

「Componetn properties」に「created」「size」「src」を追加します。

「created」と「size」をそれぞれ「作成日」「サイズ」のlabelに割り当てます。次に「src」を「iOSの画像1」のsrcに割り当てます。

このコマンドで追加したコンポーネントをReactプロジェクトに取り込みましょう。

amplify pull

「App.tsx」を以下のように変更しましょう。

import React, { useEffect, useState } from 'react';
import './App.css';

import { Collection, withAuthenticator } from '@aws-amplify/ui-react';

import { Storage } from "aws-amplify"
import AddButton from "./ui-components/AddButton";
import ImageCard from "./ui-components/ImageCard";

type Image = {
  src: string;
  size: string;
  created: string;
}

function App() {
  const [imgList, setImgList] = useState<Image[]>([]);

  useEffect(() => {
    loadImage();
  },[]);

  async function loadImage() {
    const imgs: Image[] = [];
    const result = await Storage.list('', { level: 'private' });
    for(let i = 0; i < result.length; i++){
      const url = await Storage.get(result[i].key!, { level: 'private' });
      const img: Image = {
        src: url,
        size: result[i].size!.toString() + 'byte',
        created: result[i].lastModified!.toDateString()
      };
      imgs.push(img);
    }
    setImgList(imgs);
  }

  function addImage() {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*'
    input.oninput = (element) => {
      const target = element.target as HTMLInputElement;
      if( target.files && target.files.length ){
        const file = target.files[0];
        Storage.put(file.name, file, { level: 'private' }).then(() => loadImage())
      }
    }
    input.click();
  }

  return (
    <div className="App">
      <AddButton onClick={addImage}/>
      <Collection
        type="grid"
        templateColumns="1fr 1fr 1fr"
        gap="30px"
        autoFlow="row"
        alignItems="stretch"
        justifyContent="stretch"
        items={imgList || []}
      >
        {(item, index) => (
          <ImageCard
            key={index}
            {...item}
          />
        )}
      </Collection>
    </div>
  );
}
export default withAuthenticator(App);

loadImageでは、名前のとおり画像を読み込む処理を記述しています。

Storage.list()でそのユーザのprivateフォルダに入っている画像の「Key」の配列が取得できます。

Storage.get()で、先ほど取得したKeyからURLを生成します。

ちなみに、Storage.list()Storage.get() の 第二引数に { level: 'protected' }を指定すると、protectedフォルダにアクセスすることができます。levelのデフォルトでpublicになっています。

  async function loadImage() {
    const imgs: Image[] = [];
    const result = await Storage.list('', { level: 'private' });
    for(let i = 0; i < result.length; i++){
      const url = await Storage.get(result[i].key!, { level: 'private' });
      const img: Image = {
        src: url,
        size: result[i].size!.toString() + 'byte',
        created: result[i].lastModified!.toDateString()
      };
      imgs.push(img);
    }
    setImgList(imgs);
  }

続いて、addImageでは、画像をアップロードする処理を記述しています。

まず、input要素を生成し、適当にプロパティーを設定していきます。

次に画像が選択された際に、Storage.put() でファイルをアップロードし、成功したらloadImageを呼び出し再読み込みするようにしています。

こちらも、第三引数にlevelを指定してあげると、protectedやprivateにアップロードできるようになります。

  function addImage() {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*'
    input.oninput = (element) => {
      const target = element.target as HTMLInputElement;
      if( target.files && target.files.length ){
        const file = target.files[0];
        Storage.put(file.name, file, { level: 'private' }).then(() => loadImage())
      }
    }
    input.click();
  }

これらの関数を、Amplify Studioが生成したUIコンポーネントと繋げてやります。

return (
    <div className="App">
      <AddButton onClick={addImage}/>
      <Collection
        type="grid"
        templateColumns="1fr 1fr 1fr"
        gap="30px"
        autoFlow="row"
        alignItems="stretch"
        justifyContent="stretch"
        items={imgList || []}
      >
        {(item, index) => (
          <ImageCard
            key={index}
            {...item}
          />
        )}
      </Collection>
    </div>
  );

アップロードできました~!

最後に

今回は、フォトストレージを作成しましたが、ストレージとの接続が簡単にできましたね!

あと少し慣れてきて、作業の流れが分かってきたので、結構短時間でアプリを作れるようになってきました!

ただ、画像のUrlは一定時間で無効になっちゃうので、フォトストレージには不向きでした。

また、気が向いたらAmplify Studioについてブログを書いていこうと思います!