🐡

AppSheetとAWSを連携してイベント管理するアプリを作ってみた(前編)

2023/08/15に公開

はじめに

みなさま、はじめまして。

お盆に入りまとまった休みが取れたのですが、特にやることもないので普段使ってるAWSと触ったことがないAppSheetで何かできないかと思いました。
なので今回はイベントを管理するアプリを作ってみたいと思います。
(記事の執筆はほぼ初めてなので優しく指摘してくれたら嬉しいです)

アプリの流れ

1.予約可能日を管理者が登録
2.参加者はイベントの予約日等の情報を登録し申し込み
3.管理者がイベントの申し込み情報を確認

使用技術

  • React: 参加者がイベントを閲覧・予約を行う。(この記事ではでてきません)
  • AppSheet: イベントの管理者が、予約の確認・管理を行う。
  • API Gateway: データの取得や更新のリクエストを処理。(この記事ではでてきません)
  • DynamoDB: イベントの予約情報や予約可能日を保存。
  • AWS CDK(TypeScript): AWSのインフラをコード化。

構成

AppSheet連携に必要なAWS側のリソース作成

AppSheetとAWSを連携するためには、IAMユーザーとS3バケットが必要です。
さらに、データソースとして活用するDynamoDBも含めて、CDKを用いて構築を行います。

CDKプロジェクトの立ち上げ

フロントエンドとバックエンドのコードはモノレポで管理するので下記の手順でCDKを立ち上げます。

mkdir event-reservation-app && cd $_
mkdir backend && cd $_
cdk init --language typescript

コンストラクタの作成

次にlib直下にconstructディレクトリを作成しdata-store.tsiam.tsを作成します。
(おそらく今回のようなライフサイクルが異なるリソースを構築する場合、スタックそのものを分割した方がいいと思うのですが、記事用かつ依存関係の視点から1つのスタックで全て構築します)

data-store.ts
data-store.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_dynamodb as dynamodb, aws_s3 as s3 } from "aws-cdk-lib";

export class DataStoreConstruct extends Construct {
  public eventAvailableDateTableName: string;
  public eventReservationTableName: string;
  public appSheetBucketName: string;

  constructor(scope: Construct, id: string) {
    super(scope, id);

    // イベントの開催日を管理するテーブル
    const eventAvailableDateTable = new dynamodb.Table(this, 'EventAvailableDateTable', {
      tableName: 'EventAvailableDateTable',
      partitionKey: {
        name: 'date',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    this.eventAvailableDateTableName = eventAvailableDateTable.tableName;

    // イベントを予約するテーブル
    const eventReservationTable = new dynamodb.Table(this, 'EventReservationTable', {
      tableName: 'EventReservationTable',
      partitionKey: {
        name: 'mailAddress',
        type: dynamodb.AttributeType.STRING,
      },
      sortKey: {
        name: 'eventDate',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    this.eventReservationTableName = eventReservationTable.tableName;

    // AppSheet用のバケット
    const appSheetBucket = new s3.Bucket(this, 'AppSheetBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    this.appSheetBucketName = appSheetBucket.bucketName;
  }
}
iam.ts
iam.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_iam as iam } from "aws-cdk-lib";

export interface IamConstructProps extends cdk.StackProps {
  eventAvailableDateTableName: string;
  eventReservationTableName: string;
  appSheetBucketName: string;
}

export class IamConstruct extends Construct {
  public apiGatewayRole: iam.Role;

  constructor(scope: Construct, id: string, props: IamConstructProps) {
    super(scope, id);

    // AppSheet用のユーザー
    const appSheetUser = new iam.User(this, 'AppSheetUser');
    appSheetUser.addToPolicy(new iam.PolicyStatement({
      resources: [
        `arn:aws:dynamodb:${props.env?.region}:${props.env?.account}:table/${props.eventAvailableDateTableName}`,
        `arn:aws:dynamodb:${props.env?.region}:${props.env?.account}:table/${props.eventReservationTableName}`,
      ],
      actions: [
        'dynamodb:BatchGet*',
        'dynamodb:DescribeTable',
        'dynamodb:Get*',
        'dynamodb:Query',
        'dynamodb:Scan',
        'dynamodb:BatchWrite*',
        'dynamodb:Delete*',
        'dynamodb:Update*',
        'dynamodb:PutItem'
      ],
    }));
    appSheetUser.addToPolicy(new iam.PolicyStatement({
      resources: [
        '*'
      ],
      actions: ['dynamodb:ListTables'],
    }));
    appSheetUser.addToPolicy(new iam.PolicyStatement({
      resources: [`arn:aws:s3:::${props.appSheetBucketName}`, `arn:aws:s3:::${props.appSheetBucketName}/*`],
      actions: [
        's3:Get*',
        's3:List*',
        's3:PutObject'
      ],
    }));
  }
}

Stackファイルでコンストラクタを呼び出し

backend-stack.ts
backend-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { DataStoreConstruct } from './construct/data-store';
import { IamConstruct } from './construct/iam';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const dataStoreConstruct = new DataStoreConstruct(this, 'DataStoreConstruct');
    const iamConstruct = new IamConstruct(this, 'IamConstruct', {
      eventAvailableDateTableName: dataStoreConstruct.eventAvailableDateTableName,
      eventReservationTableName: dataStoreConstruct.eventReservationTableName,
      appSheetBucketName: dataStoreConstruct.appSheetBucketName,
      env: {
        region: this.region,
        account: this.account,
      }
    });
  }
}

デプロイ及びアクセスキー作成

backendディレクトリ直下でデプロイを実行します。

cdk deploy

下記コマンドでアクセスキーとシークレットアクセスキーを作成します。

aws iam create-access-key --user-name appsheet-user

AppSheetで管理画面構築

AppSheetで管理画面構築を構築していきます。
今回はブログではイベント予約可能日を確認・追加・削除できるようにします。

下記からAppSheetのホームにログインします。
AppSheet Home

データソース追加

AWS側で構築したリソースを元にDyanmoDBをデータソースとして追加します。

「MyAccount」→「Sources」→「New Data Sourc」eをクリックします。

データソースとして接続可能なサービスが一覧で表示されるので「Cloud Database」を選択します。

データベースへの接続情報が必要なため入力していきます。
Typeを「DynamoDB」、リージョンを「ap-northeast-1」にし後はCDKで作成したリソースを元に入力しまします。

「Test」をクリック、問題なければ「Authorize Access」をクリックし 「database-数字」でデータソースが追加されれば成功です。

アプリ作成

やっとアプリの作成に入ります。
「MyApp」をクリックし、「Create」→「App」→「start existing with data」をクリックします。

アプリ名を入力し、「Choose your data」をクリック。

「CloudDatabase」をクリック。

次にTable名を指定する必要があるので、「EventAvailableDateTable」を選択します。
そして下記のような画面がでてくれば成功です。

カラムの設定

DynamoDBのキーに合わせてAppSheet側でカラムの設定を行なっていきます。
一番左の「Data」をクリックします。

下記のようにカラムを編集します。
-_RowNumberのKeyとREQUIREのチェックを外す
-_Attribute1のカラム名をdateに変更
-dateのKEY,LABEL,SHOW,REQUIREにチェックを入れる。

最後に右上の「Save」をクリックします。

プレビュー

右側にあるプレビューの「Desktop」をクリックします。

プレビュー画面の右上の「Add」から日付を選択し「Save」します。


これで予約可能日がAWSのDyanmoDBに保存されます。

まとめ

とりあえず予約可能日の管理ができるところまで構築しましたが、フロントエンドが苦手な自分にとっては管理画面だけでもAppSheetで手抜きできるのは便利かと思いました。
ただ今回AppSheetを使いたいがために無理やりこの構成したので、実務で使うかはその時改めて検討したいですね。
あと記事が不慣れなためまとめきれず全体としては40%くらいなので次はAPIGatewayとフロントエンドの構築を行なっていきたいと思います。

O-KUN Tech Blog

Discussion