Personiumのセルに ODataライブラリ o.js を使って読み書き


# はじめに

PDS(Personal Data Store) の OSS 「Personium」ですが、
肝心の個人データ管理には `OData` と `WebDAV` という標準化されたプロトコルを使用します。

今回はODataについて触り方を紹介します。Personium特有の制約事項などもあるので、
詳細は[ドキュメント](https://personium.io/docs/ja/apiref/000_Rest_API_Reference/)を読むべきなのですが、
全部読んでいると日が暮れてしまいますので([例](https://personium.io/docs/ja/apiref/366_Retrieve_Entity/))、
ライブラリなどを使用して簡単に入門してみます。

Personiumについてはこちら → [Personium とは何か？を個人的にまとめてみた](https://qiita.com/yoh1496/items/e4df4e3585af0158a246)


# 準備

`o.js` を使う前に今回使用する環境を準備します。

## Personiumのデータ構造

Personiumでは、`ユニット`の中に`セル`というものを作成し、
1人1人がそれを管理します。

管理にあたって、セルの中には用途ごとに`ボックス` を作成し、
ボックスの中には画像データなどの非構造データや、表データのような構造データを保存できます。

ボックス内の構成の管理に使用するプロトコルが `WebDAV` で、
ボックス内のコンテンツの1つとして、`OData` で管理できる表データがある形です。

## セルの管理

Personiumのデモサーバーでは、いくつかのアプリがデフォルトでインストールされていて、
その中の「セルマネージャ」を使用すると簡単なセル管理が体験できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/3b4c232e-391d-6ca5-7a62-1c2002c491c1.png)

セルの管理の詳細については、セル管理（いずれ書きたい）の記事に任せるとして、ODataを作成していきます。

## ODataの作成

今回はセルマネージャを使用して自分のセルに「odata-test」というボックスを作り、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/66f0e6fa-23d1-6f8f-df99-af56a3887b47.png)

その中に「sample」という名前で OData を作成しました。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/625f09fe-662a-ee74-9b99-a1b4afde404e.png)

## EntityTypeの作成

ODataが作成できたら、次はその中に保存するデータの型（スキーマ）を定義していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/1f8f4a0d-951e-b554-e7e5-6343ec68edef.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/74b6d644-c85f-7832-3161-bc3db10077dd.png)

型「sampletype」

|名前|型|
|:--|:--|
|title|Edm.String|
|description|Edm.String|
|num|Edm.Int32|
|price|Edm.Double|

# `o.js` で OData に触れてみよう

それでは触っていきます。

## インストール

まずは `o.js` をインストールします。

```cmd
npm install --save odata
```

## 準備

ここから先は `oHandler` というインスタンスを使い回します。

```javascript
const {o} = require('odata');

const token = "<YOUR_TOKEN>";
const defaultHeaders = { "Content-Type": "application/json" }

const oHandler = o('https://<YOUR_CELL_FQDN>/odata-test/sample/', {
  headers: { "Authorization": `Bearer ${token}` , ...defaultHeaders },
});
```

こんな感じです。 `<YOUR_TOKEN>` に入るトークンはセルマネージャから取得することもできます。
（ROPCなどで認証して取ってくるのが正当な手順ですが・・・）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/abf3d9a3-2f26-656f-ec06-5d6491c4b6a7.png)

## EntityType取得

まずはEntityTypeを取得してみます。[EntityType取得](https://personium.io/docs/ja/apiref/347_Retrieve_EntityType/)

`$metadata/EntityType` を叩けばいいらしい。

```javascript
const response = await oHandler
  .get('$metadata/EntityType')
  .query({
    $format:"json"
  });
console.log(JSON.stringify(response, null, 2)
```

```json
{
  "d": {
    "results": [
      {
        "__metadata": {
          "uri": "https://<YOUR_CELL_FQDN>:443/odata-test/sample/$metadata/EntityType('sampletype')",
          "etag": "W/\"1-1588926355822\"",
          "type": "ODataSvcSchema.EntityType"
        },
        "Name": "sampletype",
        "__published": "/Date(1588926355822)/",
        "__updated": "/Date(1588926355822)/",
        "_AssociationEnd": {
          "__deferred": {
            "uri": "<略>/odatatest/sample/$metadata/EntityType('sampletype')/_AssociationEnd"
          }
        },
        "_Property": {
          "__deferred": {
            "uri": "<略>/odata-test/sample/$metadata/EntityType('sampletype')/_Property"
          }
        }
      }
    ]
  }
}
```

ちゃんと定義されていることが伺えますね。

なお、下記スクリプトでプロパティ一覧を取得すると

```javascript
const response = await oHandler
  .get("$metadata/EntityType('sampletype')/_Property")
  .query({
    $format:"json"
  });
console.log(response.d.results.map(x => `${x.Name}: ${x.Type}`));
```

```cmd
[
  'title: Edm.String',
  'description: Edm.String',
  'num: Edm.Int32',
  'price: Edm.Double'
]
```

いい感じです。

## Entity作成

続いて、先程定義した型でEntityを登録してみます。[Entity作成](https://personium.io/docs/ja/apiref/364_Create_Entity/)

```javascript
const dat = {
  title: 'hoge',
  description: 'fuga',
  price: 100,
  num: 5
}
const response = await oHandler
  .post('sampletype', dat)
  .query({
    $format:"json"
  });
```

セルマネージャで確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/8c8858c1-4799-98ab-18e7-01465a13d617.png)

たくさん作ってみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/816a89b9-3743-1648-373c-9f2956eb3b8f.png)

できてる！！

なお、 `num: 0.325` など、本来 Int32 型のプロパティに Double を入れたりすると、
「400 Bad Request」が返ってきます。

## Entity取得

```javascript
const response = await oHandler
  .get('sampletype')
  .query({
    $format:"json"
  });
console.log(response.d.results.map(x => ({ title: x.title, price: x.price})));
```

結果

```cmd
[
  { title: 'hoge', price: 100 },
  { title: 'hoge1', price: 100 },
  { title: 'hoge2', price: 100 },
]
```

クエリももちろん書けます。

```javascript
const response = await oHandler
  .get('sampletype')
  .query({
    $format:"json",
    $filter: "title eq 'hoge' and num eq 0"
  });
console.log(response.d.results.map(x => ({ title: x.title, num: x.num})));
```

結果

```cmd
[ { title: 'hoge', num: 0 } ]
```

いいですね。

# おまけ

使用した上でいくつか引っかかったポイントを紹介。

## Proxy配下で使用する

`o.js` 内部で使用している `node-fetch` はデフォルトでは
プロキシ環境変数（http_proxy, https_proxy）は読んでくれません。

[node-fetch をプロキシ環境で使う](https://qiita.com/shinsuke-noguchi/items/b08d905ff6c47e957943)

そこで、上記のモジュールを使用させてもらいます。

```cmd
npm install --save node-fetch-with-proxy
```

でもって、

```javascript
const oHandler = o('<ODATA_ENDPOINT>');

global.fetch = require('node-fetch-with-proxy');

const response = await oHandler.get('hoge').query({});
```

といった形で、`o` 関数を実行した後にfetchを上書きします。

`o`関数ではこのように、呼ばれた際にブラウザか否かを判定して、
`fetch`がなければ polyfill を実行するようになっています。

```javascript
// https://github.com/janhommes/o.js/blob/master/src/o.ts#L27

// polyfill fetch if we have no fetch
const env = typeof window !== "undefined" ? window : global;
if (!("fetch" in env) && !config.disablePolyfill) {
  require("cross-fetch/polyfill");
}
```

自前で polyfill して、 config.disablePolyfill を true にするでも良いのですが、
今回はとりあえずこれで。

# 終わりに

今回は `o.js` というライブラリを使用して ODataというプロトコルでデータを
触ってみる、という記事を書いてみました。

伝えたかったことは、ODataという標準化されているプロトコルを採用していることで、
既存のライブラリを使用することが可能であるという点です。

Personiumを使ってみたい、もっと知りたい、開発に参加したい、という方は
[Personiumコミュニティ](https://personium.io/ja/index.html) までお願いします！