mk-toolブログ

エンジニアと家のことをごちゃごちゃと書いてます

CloudStorageとのCORSを解消する

手順

  1. Cloud SDK を install
  2. 既存の設定を取得する
  3. cors対策用のファイルを作成
  4. デプロイ
  5. 設定が書き換わっていることを確認

詳細

1. Cloud SDK を install

Cloud SDK Command Line Tools  |  Cloud SDK: Command Line Interface

2. 既存の設定を取得する

gsutil cors get gs://BUCKET_NAME

3. cors対策用のファイルを作成

touch cors.json

そして、2で取得した既存の値を入れる。 それに加えて、新しく追加したいドメイン名を加える。

4. デプロイ

gsutil cors set cors.json gs://BUCKET_NAME

5. 設定が書き換わっていることを確認

gsutil cors get gs://BUCKET_NAME

CloudStorageとのCORSを解消する

手順

  1. Cloud SDK を install
  2. 既存の設定を取得する
  3. cors対策用のファイルを作成
  4. デプロイ
  5. 設定が書き換わっていることを確認

詳細

1. Cloud SDK を install

Cloud SDK Command Line Tools  |  Cloud SDK: Command Line Interface

2. 既存の設定を取得する

gsutil cors get gs://BUCKET_NAME

3. cors対策用のファイルを作成

touch cors.json

そして、2で取得した既存の値を入れる。 それに加えて、新しく追加したいドメイン名を加える。

4. デプロイ

gsutil cors set cors.json gs://BUCKET_NAME

5. 設定が書き換わっていることを確認

gsutil cors get gs://BUCKET_NAME

Sequelizeで多対多のテーブルジョインを定義する

一つの記事には複数の著者が存在し(共同著者)、著者には複数の記事が結びつく場合、中間テーブルを利用して多対多の状態をつくれるようにしなければならない。

そのように行う方法を説明する。

これには、 belongsToMany を利用することで多対多のデータ構造を作成することができる。 簡易的な書き方を紹介して、その次に、 asforeignKey を使って細かいことをできる指定の仕方を紹介する。

定義は以下。

Article.belongsToMany(author.factory(sequelize), {
  through: "ArticleAuthor"
});

使い方は以下。

const findAllIncludeParams: IncludeOptions[] = [];

findAllIncludeParams.push({
  model: Author,
  attributes: [],
  required: true
});

Article.findAndCountAll({
  include: findAllIncludeParams
})

これにもっと細かい指定をすると、以下のようになる。

もし、 Article-Author 間の連結が1種類でない場合は、 as で別名をつけることで、 複数の Article-Author の連結を行うことができる。

定義は以下。

Article.belongsToMany(author.factory(sequelize), {
  as: "essay",
  through: "articleAuthor",
  foreignKey: "articleId",
  otherKey: "authorId"
});
Article.belongsToMany(author.factory(sequelize), {
  as: "novel",
  through: "articleAuthor",
  foreignKey: "articleId",
  otherKey: "authorId"
});

使い方は以下。

findAllIncludeParams.push({
  as: "aaa",
  model: Author,
});

参考

sequelize.org

resolveJsonModuleでDate型を使いたい

resolveJsonModule では json を読みこみ、 typeof を与えてあげることで、モックデータから型を自動で生成してくれる。 しかしながら、 json を読み込む部分では、 Date 型を扱いたくても、 string と判定されてしまう。

{
  ...
  createdAt: "2000-01-01 00:00:00"
  ...
}

これを Date 型にするには、 json を読み込むのではなく、以下のような ts ファイルを読み込ませ、 mockTypeimport することで Date 型を認識させることができる。

export const mockType = {
  ...
  createdAt: new Date("2000-01-01 00:00:00")
  ...
}

firebaseのチャットで画像を扱うために

LINEのようなチャットツールでは、チャットしている時系列に画像を表示したり、それとは別に、写真だけを一覧表示することができる。

これがRDBMSでは頭を捻らなくても実装することができるが、firebaseのようなNoSQLでこれを実装するためにはどのようにすれば良いか。

その場合は、 collectionGroup() を使用してすることで実装することができる。

そのために以下のようなコレクションを定義する。

※C: コレクション ※d: ドキュメント

rooms(C)
  - room1(d)
    - name
    - description
    - messages(C)
      - XXX(d)
        - body
          - images(C)
    - members(C)
      - member1(d)
        - name
        - photoUrl

写真を時系列のメッセージと共に流す際には、

db.collection("rooms").doc("room1").collection("messages")

のように普通通りに取得をして、画像だけを一覧したい場合は

db.collection("rooms").doc("room1")collectionGroup("images")

を実行する。

collectionGroupを使うことで、他のmessagesドキュメントの写真を串刺しにして取得することができる。

SequelizeでwhereHasを行う

include内のrequired値をtrueにすることでwhereHasと同じ挙動の動作をさせることができる。 falseの場合は、ORMのリレーションの結果が0件でも取得できる。

attributesを[]にすることで、whereHasだけを利用することも可能。

XXX.findAll({
    where: findAllParams,
    include: [
        {
            model: YYY,
            where: { YYYId: 3 },
            attributes: ['YYYId'], // カラムの絞り込み
            required: true
        }
    ],
    limit: limit,
    offset: offset
})

firebaseのAuthenticationとfirestoreを利用したセキュリティルール設定

ここではセキュリティルールを考えた際にfirestoreはどういったデータ構造にするか、ということを説明する。

詳細の設定については、 公式ブログ に譲る。

チャットアプリのような双方向の通信を実現する際は、 firebase を利用することで簡単に実装できる。

実際には、アプリケーション内で firestore のデータを監視して、データの追加/変更/削除が発生したら自身で用意したイベントを呼び出すことで、相手の 発信 を受け取ることができる。

ただ、 firestore は正しいセキュリティルールを設定しないと、 誰でも ストレージにアクセスができるので、必ずルールを設定しなければいけない。

ここで、 firestore を以下のように組み立てるとする(これは後に困るやり方)。

Collection Document Collection/data Document
chatRoom XXX member(C) YYY
message(C) ZZZ
name(d)
description(d)

アクセス制御を行うには、以下のような設定を行う。

match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/chatRoom/$(roomId)/member/$(request.auth.uid));
}
// 上記以外全て拒否

このようにすることで、 /chatRoom にアクセスして全データを取得するときに、 /chatRoom/{roomId} の条件にヒットしないものは取得されない、と思ったが、このパス /chatRoom/{roomId} はリクエストパスを表現しているため、 このようにアクセス制御を行うと、 /chatRoom へのルールが設定されていないため、アクセス自体が拒否されてしまう。 /chatRoom の全取得で権限があるデータだけを取得する、という操作はできないことがわかる。

このことから、 firestore のデータ構造を以下のようにする必要がある。user コレクションには、 chatRoom の情報を非正規化して保持している。ただ、非正規化が行われるということは、チャットルーム名の変更などが発生した場合は、非正規化が発生した部分を全て変更しなければならない。firebaseでは、 collectionGroup() というメソッドでサブコレクションを串刺しにして取得することができるので、collectionGroup(YYY) とすることで、非正規化されたデータを一括で書き換えることができるようになる。

(※ user のサブコレクションの userRoomroom にしなかった理由はサブコレクションを串刺しにしたいときに、 room は他のサブコレクション名とバッティングする恐れがあるため、 userRoom とした)

Collection Document Collection/data Document data
chatRoom XXX message(C) ZZZ
name(d)
description(d)
user XXX userRoom(C) YYY name(d)
description(d)

このようにすることで、チャット一覧の取得は、 /user/{user}/userRoom で取得するようになる。このとき、認証されたユーザしか /user/{user}/userRoom のデータにアクセスできない。チャットの詳細を取得するときは、 /chatRoom/{roomId} にアクセスして、認証されているユーザがこのルームへのアクセス権限があるかを判定している。

match /user/{user}/userRoom {
      allow read, write: if user == request.auth.uid;
}
match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/user/$(request.auth.uid)/userRoom/$(roomId));
}
// 上記以外全て拒否

これでセキュアな状態(非正規化ではあるもののデータ更新に困らない)のモノになるのではないだろうか。