[Keystone] 画像とファイル

画像とファイル

Keystone のフィールドには、画像やファイルなどの種類が含まれています。これらを使用して、Keystone から画像やファイルを参照および (必要に応じて) 提供することができます。このガイドでは、Keystone システムで画像とファイルを構成する方法を説明し、アセットをローカルに保存するか、Amazon S3 ストレージまたは DigitalOcean Spaces などの互換性のある S3 プロバイダを使用することができます。

Keystone でのアセットストレージの仕組み

Keystone では、設定ファイルで定義したストレージオブジェクトを介してファイルや画像を管理します。ストレージオブジェクト内には、ローカルや S3 互換のプロバイダを混在して設定することができます。
ストレージオブジェクトは、Keystone およびクライアントフロントエンドでアセットの保存とアクセス方法を定義します。このオブジェクトでは、以下の内容を定義します。

  • 使用するストレージの種類 - S3 またはローカル
  • ストレージを使用するためのフィールドタイプ - ファイルまたは画像
  • GraphQL API で Keystone が返す URL を生成するための関数 - アセットがアクセス可能な場所またはストレージの場所を指す
  • Keystone がアセットを保存する実際の場所 - ローカルパスまたは S3 バケットの詳細
  • Keystone がアセットを提供する場所 - ローカルの場合は serverRoute、S3 の場合はプロキシ接続を使用します。これらのオプションにより、Keystone バックエンドにルートが追加され、ファイルにアクセスできるようになります。

Keystone の設定ファイルで定義するストレージ

まず、dotenv を使用して、.env ファイルからまたは環境変数から S3 バケットと URL の詳細を取得することにします。
設定ファイルの前に、環境変数をいくつかの使いやすい名前にマッピングすることができます。

import { config } from '@keystone-6/core';
import dotenv from 'dotenv';
import { lists } from './schema';

dotenv.config();

const {
  S3_BUCKET_NAME: bucketName = 'keystone-test',
  S3_REGION: region = 'ap-southeast-2',
  S3_ACCESS_KEY_ID: accessKeyId = 'keystone',
  S3_SECRET_ACCESS_KEY: secretAccessKey = 'keystone',
  ASSET_BASE_URL: baseUrl = 'http://localhost:3000',
} = process.env;
/** ... */

S3 にアセットを保存する

次に、s3 ストレージオブジェクトを追加できます。以下のオブジェクトは my_s3_files と呼ばれ、後でフィールド構成で使用する名前です。名前は重要ではなく、あなたにとって意味のある任意の名前に調整することができます。
keystone.ts ファイルの config オブジェクト内に、以下の構成を追加してください。

storage: {
  my_s3_files: {
    kind: 's3', // this storage uses S3
    type: 'file', // only for files
    bucketName, // from your S3_BUCKET_NAME environment variable
    region, // from your S3_REGION environment variable
    accessKeyId, // from your S3_ACCESS_KEY_ID environment variable
    secretAccessKey, // from your S3_SECRET_ACCESS_KEY environment variable
    signed: { expiry: 3600 }, // (optional) links will be signed with an expiry of 3600 seconds (an hour)
  },
  // ...
},

DigitalOcean Spaces などの S3 互換プロバイダを使用している場合は、エンドポイントを追加する必要があります。

storage: {
  my_s3_files: {
    // ...
    endpoint: 'https://ap-southeast-2-region.digitaloceanspaces.com' // or et cetera,
  },
  // ...
},

ローカルにアセットを保存する

アセットは、ローカルディスクに保存することもできます。以下のオブジェクトは my_local_images と呼ばれ、/public/images に保存され、Keystone によって /images で提供されます。

/** config */
storage: {
  my_local_images: {
    // Images that use this store will be stored on the local machine
    kind: 'local',
    // This store is used for the image field type
    type: 'image',
    // The URL that is returned in the Keystone GraphQL API
    generateUrl: path => `${baseUrl}/images${path}`,
    // The route that will be created in Keystone's backend to serve the images
    serverRoute: {
      path: '/images',
    },
    // Set serverRoute to null if you don't want a route to be created in Keystone
    // serverRoute: null
    storagePath: 'public/images',
  },
  /** more storage */
}

GraphQLで返されるURLをカスタマイズする

画像やファイルのフィールドタイプを使用する場合、Keystone はGraphQL API クエリで次のように返します。

image {
  id
  filesize
  width
  height
  extension
  ref
  url
}

このクエリで返される URL は、generateUrl 関数を使用して設定できます。この関数は、フルファイル名と拡張子であるパスを取得し、GraphQL API で必要な URL を返します。

全体をまとめると以下のとおり

以下の例では、2つのアセットストアを定義しています。1つはファイルを保存するための S3 であり、もう1つはイメージを保存するためのローカルです。

// keystone.ts

import { config } from '@keystone-6/core';
import dotenv from 'dotenv';
import { lists } from './schema';

// We are going to use dotenv to get our variables from a .env file or from set environment variables
dotenv.config();

 const {
   // The S3 Bucket Name used to store assets
   S3_BUCKET_NAME: bucketName = 'keystone-test',
   // The region of the S3 bucket
   S3_REGION: region = 'ap-southeast-2',
   // The Access Key ID and Secret that has read/write access to the S3 bucket
   S3_ACCESS_KEY_ID: accessKeyId = 'keystone',
   S3_SECRET_ACCESS_KEY: secretAccessKey = 'keystone',
   // The base URL to serve assets from
   ASSET_BASE_URL: baseUrl = 'http://localhost:3000',
 } = process.env;

export default config({
  db: {
    provider: 'sqlite',
    url: process.env.DATABASE_URL || 'file:./keystone-example.db',
  },
  lists,
  storage: {
    // The key here will be what is referenced in the image field
    my_local_images: {
      // Images that use this store will be stored on the local machine
      kind: 'local',
      // This store is used for the image field type
      type: 'image',
      // The URL that is returned in the Keystone GraphQL API
      generateUrl: path => `${baseUrl}/images${path}`,
      // The route that will be created in Keystone's backend to serve the images
      serverRoute: {
        path: '/images',
      },
      storagePath: 'public/images',
    },
    // The key here will be what is referenced in the file field
    my_s3_files: {
      // Files that use this store will be stored in an s3 bucket
      kind: 's3',
      // This store is used for the file field type
      type: 'file',
      // The S3 bucket name pulled from the S3_BUCKET_NAME environment variable above
      bucketName,
      // The S3 bucket region pulled from the S3_REGION environment variable above
      region,
      // The S3 Access Key ID pulled from the S3_ACCESS_KEY_ID environment variable above
      accessKeyId,
      // The S3 Secret pulled from the S3_SECRET_ACCESS_KEY environment variable above
      secretAccessKey,
      // The S3 links will be signed so they remain private
      signed: { expiry: 5000 },
    },
  },
});

リスト内で画像やファイルを使用する方法

Keystone でストレージ構成を設定した後、画像とファイルのフィールドタイプを使用できます。
既存のリスト内で、以下のように画像またはファイルのフィールドを使用します。

lists: {
  user: list({
    fields: {
      /** other fields */
      avatar: image({ storage: 'my_local_images' }),
      someFile: file({ storage: 'my_s3_files' }),
    }
  })
}

リレーションシップの例

複数のリストまたはドキュメントフィールドで再利用およびアクセスできるように、画像 (およびファイル) を設定する場合、ギャラリーリストを設定し、関連付けを使用して参照することができます。また、関連付けを使用すると、削除後にアセットを保持する場合には、代わりに関連付けを削除し、アセットを別々にクリーンアップできます。
例えば、以下のようなスキーマがあるかもしれません。

// schema.ts
import { list } from '@keystone-6/core';
import { text, image } from '@keystone-6/core/fields';

export const lists = {
  Image: list({
    fields: {
	  name: text({
		  validation: {
	    isRequired: true,
		  },
	  }),
	  altText: text(),
	  image: image({ storage: 'my_local_images' }),
    }
  }),
  Page: list({
    fields: {
      name: text(),
      context: text(),
      images: relationship({ ref: 'Image', many: true })
    }
  })
};

画像とファイルの Content-Type の違い

  • Keystone は、ファイルを提供する場合には Content-Type として application/octetstream を使用します。
  • 一方、画像を提供する場合には、拡張子によって設定された MIME タイプから Content-Type が設定されます。
    これは、画像に対しては拡張子が信頼できるが、ファイルに対しては信頼できないことを意味します。これは、ローカルおよび S3 の両方に適用されます。