SSSAPI を試す
面白そうと思ったところ。
設定用 API
(ローカルのサーバー以外で)個人利用アプリ向けに設定用 API など追加しようとするとわりとハードルが高い(認証やらコストやら)。
以前に個人用のウェブアプリの設定を外部に保存するためにうだうだ書いたメモ。
この辺に利用できるのでは。
AppSheet との併用
おそらく SSSAPI の方針に反するとは思うのだが。
スプレッドシートの更新 UI を AppSheet で作成し、API 部分に SSSAPI を利用すると楽ができるのではという期待。
ここで「いやいや AppSheet でも REST API があるじゃない」となるのだが、「AppSheet の REST API はクセが強い」ので「シンプルな SSSAPI の方がより楽をできるのでは」ということになる(という願望)。
以下は現状で AppSheet の API を使う場合。
▲ AppSheet のみ
SSAPI が間に入ると以下のように AppSheet の API を使うことがなくなる(はず)なので、少なくとも発動条件がわからない429
に悩まされることはなくなるのかなと。
▲ SSSAPI との併用
その他
以下のスクリーンショットを見たときに「これは説得力ありますわ」と思ったので。
説得力のある 1 枚
利用開始
ユーザー登録のようなものなく Google アカウントでログインする。
ここで面白いのは「権限を付与する必要はない」ということ。
この手のサービスだとだいたいは「Google スプレッドシートのすべてのスプレッドシートの参照、編集、作成、削除」などを要求されるが、SSAPI ではそれがない。
ではどうするのかというと「スプレッドシートを SSSAPI のエージェントユーザーと共有」することで解決していた。
というわけで Google アカウントでログインするだけでユーザー登録は完了した。
API 追加
ドキュメントの通りでとくに引っかかるところはなかった。
今回は以下のスプレッドシートを使っている。フィールド名などの扱いが気になったので日本語多めにしてある。
利用したスプレッドシート
API を使ってみる
設定画面を開くと API の URL が確認できる。
API の URL 確認
ブラウザー
URL クリックするとブラウザーで JSON のレスポンスを確認できる。
JSON が表示される
また、左下の「Test Tun」クリックすると「API呼び出しテスト」画面になる。
API レスポンスなどが表示できる
CLI
curl
などで利用可能。
結果
$ curl -s 'https://api.sssapi.app/XXXXXXXX-XXXX' | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "あいうえお",
"項目 3": "abc"
}
]
API の認証
認証済みドメイン
デフォルトの認証済みドメイン。
デフォルトの認証済みドメイン
Access Token を使う予定なのですべて削除。
アクセスできなくなくる。
$ curl -s 'https://api.sssapi.app/XXXXXX_XXXXXX-X' | jq
{
"error": {
"message": "'api.sssapi.app' is not allowed."
}
}
Access Token
追加
トークンはユーザーの設定画面が行う。個別の API にだけ有効なトークンも追加できる。
選択している API は後から変更できるが、トークンの内容は再表示できない。
利用
Authorization
リクエストヘッダーでトークンを渡すことで利用できる。
$ curl -s -H 'Authorization: token XXXXXXXXXX' \
https://api.sssapi.app/XXXXXXXXX | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
snip...
データを更新する
API に利用しているスプレッドシートを更新してみる。
手動更新
まずはスプレッドシートを更新する。
行の追加など行う
この時点は API からのレスポンスは更新されない。
$ curl -s -H 'Authorization: token XXXXXXXXXX' \
https://api.sssapi.app/XXXXXXXXX | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "あいうえお",
"項目 3": "abc"
}
]
API の設定画面から「Update」をクリックすると API が再ビルドされてレスポンスも変化する。
Update をクリック
しばらく待つと更新が完了し Build log で確認できる。
Build log が増えている
Build log のプレビューで新しいレスポンスを確認できる。
新しいレスポンスを確認
実際に API を実行しても更新が確認できる。
結果
$ curl -s -H 'Authorization: token XXXXXXXXXX' \
https://api.sssapi.app/XXXXXXXXX | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "ここを更新",
"項目 3": "abc"
},
{
"項目 1": 40,
"項目 2": "これは追加",
"項目 3": "abc"
}
自動更新
API の設定で自動更新を ON にしておくとスプレッドシート更新時に API が自動的にビルドされるようになる。
少し試した限りでは極端に「待たされた」と感じることはなかった。
その後、連続して保存がかかると更新がされなくなることがあった。
だいたい前回の更新から 3 分ほど待つと更新される。
スプレッドシートを一旦閉じて開くと更新されるようになる。
更新間隔に制限がある?
追記: SSSAPI 運営の方から補足いただきました。
この辺の挙動は Google Drive API の変更検知機能の仕様によるそうです(改善も検討されているそうです)。
API のクエリーパラメーター
とりあえずページネーションを試してみる。
とくに問題なく実行できる。
これは個人的にはわりとうれしい、AppSheet の API だと offest
に該当する機能がわからなかったので。
追記。
トータルの件数が不明なので終端の判定は「limit
よりも少ない件数なら EOF」とすることになるか?現在検証中。
試してみた、offset 位置から取得できる件数が少なければ limit までは取得されない。よって limit よりも少ない件数なら EOF と判断できる。
$ curl -s "https://api.sssapi.app/XXXXXX?limit=2&offset=2" | jq
[
{
"項目 1": 30,
"項目 2": "ここを更新",
"項目 3": "abc"
},
{
"項目 1": 40,
"項目 2": "これは追加",
"項目 3": "abc"
}
]
レコードの手動並べ替え
各種 Headless CMS について調べたときに「レコードを手動で並べ替える」ことができるサービスが意外と少なかった。
Contetful、GraphCMS、microCMS、prsimic の中では microCMS だけが管理画面のレコード一覧で並べ替えが可能だった(Storyblok でも可能なようだが詳細は試していない)。あとのサービスではモデルを参照関係にして参照されているレコードを並べ替えるような対応が必要となった。
SSSAPI はスプレッドシートを直接編集するのでここは期待していたがもちろん可能だった。
行を並べ替えた
$ curl -s -H 'Authorization: token XXXXXXXXXX' \
https://api.sssapi.app/XXXXXXXXX | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 40,
"項目 2": "これは追加",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "ここを更新",
"項目 3": "abc"
},
{
"項目 1": 50,
"項目 2": "さらに追加",
"項目 3": "abc"
}
]
とりあえず
これで試してみようと思っていた部分は網羅できたかなと。
ここまでの感想としては「とにかくお手軽で使いやすい」となる。
あとは予想外(予想以上)だった部分について。
Google ドライブ上のすべてのスプレッドシートへのアクセス権は要求されない
これは利用開始のときにも書いた通りで「スプレッドシート単位の共有」でサービスが実現されている。
スプレッドシート更新にあわせて API がビルドされる
試してみる前は「API 実行時に SSSAPI が毎回スプレッドシートを読みに行く」ような構成だと思っていた(いわゆるサーバーレスファンクション的な挙動)。
実際の挙動としては API をビルドすることで「その時点のスプレッドシートを読み込み API のステートとする」という構成だった(Azure の Durable Functions のような感じ?)。
これについては「スプレッドシートの更新にあわせて他のサービスでの処理をキック」したいような場合、SSAPI のビルドが完了したかわかりにくいというのがある。
追記: 他サービスとの連携について SSSAPI 運営の方から補足いただきました[1]。
しかし、更新の少ないマスターデータ的な使い方であればパフォーマンス的にはこちらの方が有利だと予想できる(紹介記事などを見ると実際に速いという結果が出ているらしい)。
また(利用プランにもよるが) Build log には以前のステート(スプレッドシートの内容)が残っていることから履歴を確認することもできる。
クエリーパラメーターでビルドの世代などを指定できると面白いのではとおもった(まぁ具体的な使い方は思いつかなかったのですが毎日更新されるフォームの履歴などに利用できそうかなと)。
追記: 履歴の取得について SSSAPI 運営の方から補足いただきました[2]。
追加
共有を解除してみる
自動更新を ON にした状態で試している。
Build log には反応なし。 API のレスポンスも変化しない。
スプレッドシートを更新しても API のレスポンスは変化しない。
行を削除してある
この状態で手動アップデートを行うと「権限がありません。共有設定を確認してください。」エラーになる(通知のメールも届く)。
これでも API レスポンスは変化しない。
API レスポンス
$ curl -s -H 'Authorization: token XXXXXXXXXX' \
https://api.sssapi.app/XXXXXXXXX | jq
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 40,
"項目 2": "これは追加",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "ここを更新",
"項目 3": "abc"
},
{
"項目 1": 50,
"項目 2": "さらに追加",
"項目 3": "abc"
}
]
再度共有する
スプレッドシートの共有設定に再度エージェントのユーザーを追加する。
これで retry をクリックすると(しなくてもよいのかも)、API がビルドされてレスポンスが最新の状態になる。
スプレッドシートを移動してみる
Google ドライブ上でスプレッドシートを別フォルダーへ移動。
Build log には反応なし。API レスポンスも変化しない。
移動した状態で変更すれば API がビルドされレスポンスも変化する。
スプレッドシートを削除してみる
Google ドライブ上でスプレッドシートを削除(ゴミ箱へ移動)。
Build log には反応なし。API レスポンスも変化しない。
「retry」も成功する。共有しているとゴミ箱にあっても投稿者はコピーを作成できると同じ理屈と思われる。
追加
API ビルド中に fetch してみる
API Optionsの自動更新を OFF にする。
自動更新を OFF
スプレッドシートを更新する。
更新前
更新後
fetch スクリプト。
#!/bin/bash
set -e
function fetch_api {
printf "%03d start\n" "${1}"
curl -s -H 'Authorization: token XXXXXXXX' \
'https://api.sssapi.app/XXXXXXX' | jq > "tmp/$(printf '%03d' "${1}").json" &
printf "%03d end\n" "${1}"
}
STEP=10
for ((j = 0; j < 20; j++)); do
for ((i = 0; i < STEP; i++)); do
fetch_api "$((j * STEP + i))" &
done
sleep ".1"
done
Update を実行しながらスクリプトも実行。
Update をクリック
スクリプトはエラーにならずに終了。
途中からレスポンスも更新後の内容に変化している。
最初と最後のレスポンス。
$ ls tmp/*.json | wc -l
200
$ cat tmp/000.json
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "あいうえお",
"項目 3": "abc"
}
]
$ cat tmp/199.json
[
{
"項目 1": 10,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 20,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 30,
"項目 2": "あいうえお",
"項目 3": "abc"
},
{
"項目 1": 40,
"項目 2": "追加",
"項目 3": "abc"
}
]
実行時の表示
$ bash test.sh
000 start
001 start
000 end
001 end
004 start
004 end
003 start
002 start
002 end
003 end
005 start
005 end
008 start
008 end
009 start
009 end
007 start
007 end
006 start
006 end
010 start
016 start
014 start
015 start
013 start
018 start
019 start
017 start
011 start
010 end
016 end
014 end
013 end
015 end
018 end
017 end
011 end
019 end
012 start
012 end
020 start
021 start
021 end
022 start
023 start
023 end
026 start
024 start
024 end
026 end
027 start
028 start
020 end
029 start
025 start
025 end
022 end
028 end
027 end
029 end
030 start
030 end
031 start
032 start
033 start
034 start
031 end
033 end
034 end
032 end
036 start
037 start
037 end
036 end
039 start
038 start
038 end
039 end
035 start
035 end
046 start
046 end
040 start
040 end
041 start
041 end
042 start
042 end
043 start
043 end
045 start
047 start
049 start
044 start
049 end
048 start
047 end
045 end
048 end
044 end
057 start
057 end
058 start
058 end
050 start
056 start
051 start
053 start
054 start
059 start
055 start
052 start
053 end
056 end
054 end
059 end
051 end
050 end
055 end
052 end
067 start
067 end
060 start
061 start
061 end
062 start
062 end
063 start
063 end
064 start
064 end
065 start
065 end
066 start
066 end
069 start
068 start
069 end
068 end
060 end
070 start
070 end
072 start
072 end
073 start
074 start
075 start
076 start
077 start
078 start
079 start
071 start
077 end
078 end
076 end
071 end
074 end
075 end
079 end
073 end
087 start
087 end
088 start
088 end
089 start
081 start
081 end
089 end
080 start
083 start
084 start
084 end
085 start
085 end
086 start
083 end
080 end
086 end
090 start
091 start
092 start
095 start
096 start
097 start
098 start
099 start
093 start
094 start
090 end
082 start
091 end
092 end
095 end
096 end
097 end
098 end
099 end
093 end
094 end
082 end
100 start
101 start
101 end
102 start
103 start
103 end
105 start
105 end
107 start
108 start
108 end
104 start
104 end
106 start
106 end
109 start
109 end
102 end
100 end
107 end
118 start
118 end
119 start
119 end
110 start
111 start
112 start
113 start
114 start
115 start
116 start
117 start
117 end
114 end
116 end
112 end
115 end
111 end
113 end
110 end
129 start
129 end
120 start
120 end
122 start
123 start
124 start
125 start
125 end
126 start
127 start
127 end
128 start
128 end
121 start
122 end
124 end
123 end
126 end
130 start
131 start
132 start
132 end
133 start
133 end
136 start
136 end
138 start
138 end
139 start
139 end
135 start
135 end
137 start
137 end
134 start
134 end
130 end
131 end
140 start
140 end
141 start
141 end
144 start
143 start
143 end
148 start
148 end
145 start
145 end
144 end
121 end
142 start
142 end
146 start
146 end
147 start
149 start
147 end
149 end
151 start
151 end
152 start
152 end
153 start
153 end
154 start
154 end
155 start
155 end
156 start
156 end
157 start
157 end
158 start
158 end
159 start
150 start
150 end
159 end
160 start
160 end
161 start
161 end
162 start
162 end
163 start
163 end
164 start
164 end
165 start
165 end
166 start
167 start
167 end
168 start
168 end
169 start
169 end
166 end
173 start
173 end
171 start
172 start
174 start
174 end
177 start
176 start
170 start
176 end
178 start
178 end
179 start
179 end
175 start
177 end
170 end
172 end
171 end
175 end
180 start
181 start
181 end
182 start
182 end
183 start
183 end
185 start
185 end
186 start
186 end
187 start
187 end
188 start
188 end
189 start
189 end
184 start
184 end
180 end
198 start
198 end
199 start
190 start
191 start
192 start
193 start
194 start
195 start
196 start
197 start
199 end
190 end
191 end
193 end
194 end
196 end
192 end
197 end
195 end
API のレスポンスが変化する Option
Option を変更した後に手動で Update を実行する必要がある。
Headless CMS 風に使う(nuxt-content + remote-cms-content)
remote-cms-content
に SSSAPI 用のクライアントを追加した。
以下のようにスプレッドシートにコンテンツを入力する。
コンテンツを入力
以下のマッピング情報を記述。
passthruUnmapped: true
flds:
- query: slug
dstName: id
fldType: id
- query: タイトル
dstName: title
fldType: string
- query: 本文
dstName: content
fldType: string
- query: カテゴリー
dstName: category
fldType: string
.env
とコマンドの実行例
RCC_CLIENT_KIND=sssapi
RCC_API_BASE_URL=https://api.sssapi.app/
RCC_CREDENTIAL__0=<ACCESS TOKEN>
RCC_MAP_CONFIG=scripts/mapconfig.yaml
API_NAME=<API PATH>
rcc --static-root static/ save --page-size 100 "${API_NAME}" content static/images
スクリプトにして実行。
$ npm run save:content
> rcc@1.0.0 save:content
> bash scripts/save_content.sh
saveRemoteContent: start max-repeat=10
ClientBase.fetch: start
ClientBase.fetch: skip=0, pageSize=100
ClientBase.fetch: count=3
saveRemoteContent: repeat=1 done
ClientBase.fetch: done
saveRemoteContent: done
$ tree content/
content/
├── index.md
├── samples.md
└── usage.md
0 directories, 3 files
Front Matter 付きの Markdwon ファイルが出来上がる。
---
_RowNumber: -1
id: usage
createdAt: 2022-01-13T08:27:27.947Z
updatedAt: 2022-01-13T08:27:27.947Z
title: 使い方
category: 使い方
position: 2
---
なにか使い方的なことを書く。
## 基本的なコマンド
- `save` - コンテンツを保存
- `watch` - 指定されたコンテンツを監視し変更があれば保存
これで nuxt-content
で利用しやすくなった。
AppSheet
上記のスプレッドシートを AppSheet のアプリにした。
既に AppSheet を使っているのでスプレッドシートの「拡張機能」メニューから AppSheet を選択するとアプリにできる。
「アプリを作成」を選択
AppSheet のデザイナー画面でカラムなど調整すると以下のように編集フォームが使えるようになる。
記事の編集
Save するとスプレッドシートも更新される。
更新が反映されている
この状態で SSSAPI の API を fetch する(Headless CMS 風で使ったスクリプトを利用)と AppSheet で入力した内容(Markdown)が保存される。
---
_RowNumber: -1
id: test1
createdAt: 2022-01-13T09:00:32.258Z
updatedAt: 2022-01-13T09:00:32.258Z
title: テストの記事
category: その他
position: 4
---
AppSheet の設定を行ったので少し編集。
なんだかんだいっても入力フォームがある方が楽といえば楽である。
あとは AppSheet ではオートセーブではなく明示的なセーブなのでスプレッドシートの更新回数が削減できるというメリットもある。
問題は、API のビルドが終了したタイミングがわからないことかな。
フォルダーを共有した場合
スプレッドシートではなくフォルダーを共有するとどうなるか?
スプレッドシートを共有した場合と同じように扱えた。
また共有しているフォルダー内のスプレッドシートから API の作成も可能だった。
が、ドキュメントには明記されてないようなので使わない方が無難かな。
追記: SSSAPI 運営の方から補足いただきました。
フォルダーの共有でも問題ないそうです。
特徴的に感じた部分を記事にした。