vte.cxによるバックエンドを不要にする開発(3.スキーマ定義と型の利用)

前回=> [vte.cxによるバックエンドを不要にする開発(2.データの登録と取得)](https://qiita.com/stakezaki/items/8ed6b1e448406cf2cd2f)

今回はエントリにユーザ定義項目を追加してデータを表示するところまでをやってみましょう。

## エントリに独自項目を追加する

前回の記事では、entryのtitleに``Hello``という文字列を入れるだけでした。
実はtitleはデフォルトで定義されている項目なのでスキーマ定義は必要ありませんでした。
デフォルト項目は基本的に[ATOM](http://www.ietf.org/rfc/rfc4287.txt)のメタ情報の項目です。それは以下のものがあります。

 * author、id、published、updatedなどは自動で値がセットされます
 * title、subtitle、summary等はユーザによって自由にセット可能です。
 * contentはコンテンツ(リソース本体)の格納場所として使われます。
 * linkは別名(alias)やキー(self)として使われます。
 * contributorはアクセス権限(ACL)を管理するために使われます。
 * rightsは設定情報などで使われます。この項目は暗号化されます。

詳しくは、[ドキュメント](https://vte.cx/documentation.html#index04)の方を参照してください。

デフォルト項目以外でユーザが独自の項目を追加したい場合はスキーマ定義が必要になります。
スキーマ定義は、vte.cx管理画面のエントリスキーマ管理タブから行えます。
以下の画面は、userというユーザ定義項目を追加した様子です。

<img width="951" alt="スクリーンショット 2019-08-21 16.51.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/365fcfc4-eaf3-720d-1548-f42c1549b353.png">

項目名(英語)にuser、日本語名にユーザ、**親項目選択に最上位**を選択して追加ボタンを押すとuser項目が追加されます。

また、userの子項目である、nameとemailを追加することもできます。
例えば以下のような構造のJSONを登録するためのエントリスキーマを作成してみましょう。

```json

{
   user: {
      name: "bar",
      email: "bar@vte.cx"
   }
}
```

項目名にname、日本語名に名前、**親項目選択にuser**を選択して追加ボタンを押します。

<img width="1097" alt="スクリーンショット 2019-08-21 17.02.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/b5ff7f36-7a58-080f-a544-50d9d0adaa01.png">

また、項目名にemail、日本語名にメールアドレス、**親項目選択にuser**を選択して追加ボタンを押します。

<img width="1099" alt="スクリーンショット 2019-08-21 17.03.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/f950b08b-dd40-bfd4-74c1-3a59984e6903.png">

すると、エントリ項目一覧では以下のようになります。日本語名やコメントはいつでも更新可能です。

<img width="1181" alt="スクリーンショット 2019-08-21 17.03.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/c8764eff-7af2-a62e-5dba-ede016d8ca57.png">

次に、以下のコマンドをターミナルから実行してください。

```
npm run download:template
```

これにより、スキーマ情報がダウンロードされ、ローカルファイル(``setup/settings/template.xml``)が更新されます。中身を覗いてみましょう。

```template.xml
<?xml version="1.0" encoding="UTF-8" ?>
<feed>
<entry>
  <content>user
 name
 email</content>
  <link href="/_settings/template" rel="self"/>
</entry>
<entry>
  <link href="/_settings/template_property" rel="self"/>
</entry>
  <entry>
    <link href="/_settings/template_property/user" rel="self"/>
    <title>ユーザ</title>
  </entry>
  <entry>
    <link href="/_settings/template_property/user.email" rel="self"/>
    <title>メールアドレス</title>
  </entry>
  <entry>
    <link href="/_settings/template_property/user.name" rel="self"/>
    <title>名前</title>
  </entry>
</feed>
```

最初のentryのcontentの中で以下のようなスキーマ情報が格納されています。userの下の行に一つスペースを空けてname、その下にemailがあります。一つスペースを空けることで子要素であることを意味します。

```
user
 name
 email
```

　これを手修正して更新することもできます。修正したら必ず、``npm run upload:template``を実行してサーバを更新してください。ちなみに、サービスを止めることなくスキーマ更新は可能であり登録済のデータが壊れることはありません。ただし、項目名の変更や追加は可能ですが、削除はできません。

## データをアップロードする

以下のようなJSONデータを/dataフォルダ上に作成し、``npm run upload:data``を実行してください。キーである``link.___href``が``/foo/2``になっていますので、``/d/foo/2``に登録されるはずです。

```sample2.json
[{
  "user": {
    "name": "bar",
    "email": "bar@vte.cx"
 },
  "link": [
      {
          "___href": "/foo/2",
          "___rel": "self"
      }
  ]
}]
```

登録されたかブラウザで確認してみましょう。``http://{サービス名}.vte.cx/d/foo?x&f``をブラウザで開いてみてください。以下のように表示されたらアップロード成功です。

<img width="427" alt="スクリーンショット 2019-08-21 17.41.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/33006a45-58f0-7210-86eb-8aae2111254e.png">

もし、表示されない場合は、``/d/foo``フォルダが正しく作成されているか確認してください。
管理画面のエンドポイント管理タブのエンドポイント一覧で以下が表示されていれば作成されています。作成されていなければ新規エンドポイント作成を行ってください。(詳しくは、[前回の記事](https://qiita.com/stakezaki/items/8ed6b1e448406cf2cd2f)を参照)

<img width="1186" alt="スクリーンショット 2019-08-21 17.45.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/59d57f61-ff56-d9a5-2659-fcab2cfb8a93.png">

## プログラムからデータを取得して表示する

前回のコードを修正して、登録したデータをプログラムから表示させてみましょう。

```index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { useState,useEffect } from 'react'
import axios from 'axios'

const App = () => {
  const [x, f] = useState(0)

  const getdata = async () => {
    try {
      axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
      const res = await axios.get('/d/foo/2?e')
      alert(`res= ${res.data.user.name} `);
    } catch (e) {
      alert('error')
      console.log(e)
    }
  }

  useEffect(() => {
    getdata()
  })

  return (
    <div>
      <button onClick={() => { f(x+1) }}>
       {x} times
      </button>
    </div>
  )

}

ReactDOM.render(<App/>, document.getElementById('container'))
```

``npm run serve:index``を実行するとブラウザが起動し、``res.data.user.name``の中身が表示されますので、以下のように``res=bar``が表示されれば成功です。（errorが表示される場合は、``npm run serve:login``を実行してログインしてください）

<img width="860" alt="スクリーンショット 2019-08-21 17.57.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/c2ebfa88-01d7-23d4-820f-a3fc5c3ee46b.png">

## 型を定義する

TypeScriptの型を利用した安全なコードの作成について説明します。

まず、``npm run download:typings``で型定義ファイルをダウンロードしてください。vte.cxではダウンロードの際、エントリスキーマの情報を元にTypeScriptの型定義ファイルを自動生成します。
ダウンロードすると、``/src/typings``フォルダの下に、``index.d.ts``ファイルが作成されますので、それを開いてみてください。以下のように、ATOM項目とユーザ定義項目が定義されているのがわかります。

```javascript

export = VtecxApp
export as namespace VtecxApp

declare namespace VtecxApp {
	interface Request {
		feed: Feed
	}
	interface Feed {
		entry: Entry[]
	}
	interface Entry {
		id?: string,
		title?: string,
		subtitle?: string,
		rights?: string,
		summary?: string,
		content?: Content[],
		link?: Link[],
		contributor?: Contributor[],
		user?:User
	}
	interface Content {
		______text: string
	}
	interface Link {
		___href: string,
		___rel: string
	}
	interface Contributor {
		uri?: string,
		email?: string
	}
	interface User {
		name?:string,
		email?:string
	}
}
```

次に、先程のソースを編集して型を追加してください。
``const entry: VtecxApp.Entry = res.data``が該当の箇所です。
これにより、``entry.user.name``がエラーになるので、空チェックを行うif文を追加してください。``if (entry.user&&entry.user.name) ``
これで、より堅牢なコードになりました。

```index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { useState,useEffect } from 'react'
import axios from 'axios'

const App = () => {
  const [x, f] = useState(0)

  const getdata = async () => {
    try {
      axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
      const res = await axios.get('/d/foo/2?e')
      const entry: VtecxApp.Entry = res.data
      if (entry.user&&entry.user.name) {
        alert(`res= ${entry.user.name} `);
      }
    } catch (e) {
      alert('error')
      console.log(e)
    }
  }

  useEffect(() => {
    getdata()
  })

  return (
    <div>
      <button onClick={() => { f(x+1) }}>
       {x} times
      </button>
    </div>
  )

}

ReactDOM.render(<App/>, document.getElementById('container'))
```

## データをプログラムから更新する

最後に、データをプログラムから更新してみましょう。
データを更新するにはPUTを使います。以下のコードでは、/foo/2 エントリの内容を書き換えます。
実行して``res=Updated``と表示されたら成功です。

```index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { useState,useEffect } from 'react'
import axios from 'axios'

const App = () => {
  const [x, f] = useState(0)

  const req: VtecxApp.Entry[] = [
    {
        user: {
	    name: 'baz',
	    email: 'baz@com'
	},
        link: [
            {
                "___href": "/foo/2",
                "___rel": "self"
            }
        ]
     }
  ]

  const putdata = async () => {
    try {
      axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
      const res = await axios.put('/d/foo',req)
      alert(`res= ${res.data.feed.title} `);
    } catch (e) {
      alert('error')
      console.log(e)
    }
  }

  useEffect(() => {
    putdata()
  })

  return (
    <div>
      <button onClick={() => { f(x+1) }}>
       {x} times
      </button>
    </div>
  )

}

ReactDOM.render(<App/>, document.getElementById('container'))
```

正しく更新されたかブラウザで確認してみましょう。``http://{サービス名}.vte.cx/d/foo?x&f``をブラウザで開いてみてください。（idのカンマの右の数字は更新回数です。以下は9回更新されたことを意味します）

<img width="410" alt="スクリーンショット 2019-08-28 10.07.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17472/aad1872c-f8ea-bef2-2f69-6f70ebca41a7.png">

今回はこれで以上です。お疲れ様でした。

次回=>[vte.cxによるバックエンドを不要にする開発(4.全文検索とOR検索)](https://qiita.com/stakezaki/items/e83dd86f1db5833e6ac1)