BaasBoxをUnityで触ってみた（ユーザ管理）

アプリに必要なAPIが自分のサーバでさくっと作れるBaasBox。その長旅の途中。
前回の記事ではBaasBoxの環境構築までやった。
[BaasBoxの環境構築でつまづいたこと](http://qiita.com/Mr_Y/items/d9f9cbe35204b4a38793)

今回はさらにユーザ管理のところまで深みへ・・・

# 使用したバージョン
1.0.0-M2

# ドキュメント
以下のリンクから今回解説する部分が見れる
http://www.baasbox.com/documentation/#user-management
※ただちょこちょこ間違いもあるので悲しみ

# サンプルについて
前回も書いたようにBaasBoxはUnity SDKがないのでサンプルコードも書いた。
サンプルコードで特筆することといえば
・[UnityWebRequest](http://docs.unity3d.com/ScriptReference/Experimental.Networking.UnityWebRequest.html)を使った（WWWクラスの次世代らしい）
・[MiniJson](https://gist.github.com/darktable/1411710)を使った
※当初は[JsonUtility](http://docs.unity3d.com/ScriptReference/JsonUtility.html)を使おうと思ったけど、JSONのキーでnewとかX-BB-SESSIONとか変数名で使えないのが出てきたので断念。GSONならいけるんだけどなぁ…

# BaasBoxAPIのキホン
どちらかもしくは両方の独自のヘッダをAPIコール時に渡す
アプリコード：X-BAASBOX-APPCODE（未ログイン時必須）
セッション：X-BB-SESSION（ログイン後必須）

# ユーザ作成
ユーザ名とパスワードが必須で、visibleXXX系はオプションとして指定可能

```csharp
private IEnumerator SignUp ()
{
	string url = "http://localhost:9000/user";
	using (UnityWebRequest request = UnityWebRequest.Post (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("Content-type", "application/json");
		request.SetRequestHeader ("X-BAASBOX-APPCODE", "1234567890");
		// リクエストボディ
		IDictionary requestBody = new Dictionary<string, object> ();
		requestBody.Add ("username", "user1");
		requestBody.Add ("password", "password");
		string bodyData = Json.Serialize (requestBody);
		UploadHandler uploadHandler = new UploadHandlerRaw (System.Text.Encoding.UTF8.GetBytes (bodyData));
		request.uploadHandler = uploadHandler;

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 201) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

コードに記したように基本的にリクエストボディはJSONで渡す。

# ユーザ作成で返ってくるJSONの意味
成功時(レスポンスコード:201の時)はこんな感じのJSONが返ってくる
ユーザ作成以外でも基本的にユーザ情報はこの形なのでここさえおさえればOK
※ただし全部が返ってくるわけではない

```json
{
  "result": "ok",
  "data": {
    "user": {
      "name": "cesare",
      "status": "ACTIVE",
      "roles": [{"name": "registered"}]
    },
    "id": "6d5d8300-4339-40a5-8688-d1547fec6e05",
    "visibleByTheUser": { },
    "visibleByFriends": { },
    "visibleByRegisteredUsers": {
        "_social": { }
    },
    "visibleByAnonymousUsers": { },
    "signUpDate": "2015-06-16T16:20:32.032+0200",
    "generated_username": false,
    "X-BB-SESSION": "7f943d99-5872-4f0e-9865-a02386ef882b"
  },
  "http_code": 201
}
```
**result**
ok
**data.user.name**
ユーザ名。同じものはつけられない。
**user.status**
ユーザが有効か無効かの状態。ただし、BaasBoxのコンソール等からユーザをSuspendをするとユーザ情報自体が取れないので、基本的にACTIVE以外は入らない
**roles.name**
ロール名が入る。これはBaasBoxのコンソールからも確認できる
**id**
不変のユーザのID（自動生成）
独自で生成したい時はプラグインのスクリプトからのみ可能。後で出てくるLinkに使う要素。
**visibleByTheUser**
visibleXXX系はユーザ情報取得時に中のJSONをそのまま返してくれるフィールド。可視性が違うだけで用途は同じで自由に使える。
visibleByTheUserフィールドはログインしたユーザのみが確認できる。なので本人しか見ないデータ引継ぎ用のメアドとかそういうのを入れられる。
他のユーザにユーザ名で検索された時にこのフィールドはnullになる。
**visibleByFriends**
ユーザ本人とフレンドが見れるフィールド
**visibleByRegisteredUsers**
ユーザとフレンドと登録ユーザが見れるフィールド
**visibleByFriends._social**
ユーザとフレンドと登録ユーザが見れるフィールド
SNSログインを使用している時に使えば便利とか
**visibleByAnonymousUsers**
anonymousなユーザも含めて全員が見れるフィールド
**signUpDate**
登録日時
**generated_username**
SNSログインをするとtrueになる
**X-BB-SESSION**
セッションID。ログインを必須とするAPIを使うときに渡す

# エラー時のJSON

```json
{
    "result": "error",
    "bb_code": 000000000,
    "message": "a message explaining the problem in plain English",
    "resource": "/login",
    "method": "POST",
    "request_header": { ......... },
    "API_version": "1.0.0-M2"
}
```

**result**
error
**bb_code**
BaasBoxのカスタムエラーコード。見かけることは少ない気がする。
**message**
エラーの詳細メッセージ
**resource**
リクエスト先
**method**
GET、POST、PUTとか
**request_header**
リクエストヘッダそのまま
**API_version**
使用しているAPIのバージョン

# ログイン
ユーザ名、パスワード、アプリコードをボディに渡す。
ユーザ作成と同じレスポンスが返ってくる

```csharp
private IEnumerator Login ()
{
	string url = "http://localhost:9000/login";
	using (UnityWebRequest request = UnityWebRequest.Post (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("Content-type", "application/json");
		// リクエストボディ
		IDictionary requestBody = new Dictionary<string, string> ();
		requestBody.Add ("username", "user1");
		requestBody.Add ("password", "newpassword");
		requestBody.Add ("appcode", "1234567890");
		string bodyData = Json.Serialize (requestBody);
		UploadHandler uploadHandler = new UploadHandlerRaw (System.Text.Encoding.UTF8.GetBytes (bodyData));
		request.uploadHandler = uploadHandler;

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```
ドキュメントのcurlは誤植でJSONになってないので注意。

# ログアウト
pushTokenを渡すとプッシュ通知も通知されないようになるらしい（まだ未検証）

```csharp
private IEnumerator Logout (string session)
{
	string url = "http://localhost:9000/logout";
	using (UnityWebRequest request = UnityWebRequest.Post (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("X-BAASBOX-APPCODE", "1234567890");
		request.SetRequestHeader ("X-BB-SESSION", session);

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# ユーザの無効化／有効化
adminアカウントからのみ使用できるので省略。

# ユーザ情報の再取得
セッションIDからユーザの情報を取得する。アプリではよく使うと思うAPI
※ドキュメントにはセッション情報(X-BB-SESSION)が返ってくるように書いているが、使用したバージョンでは戻ってこなかったので注意。

```csharp
public IEnumerator Refresh (string session)
{
	string url = "http://localhost:9000/me";
	using (UnityWebRequest request = UnityWebRequest.Get (url)) {
		// ヘッダ
		request.SetRequestHeader ("X-BB-SESSION", session);

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# ユーザ情報の更新
visibleXXXを更新する
どういうJSONにするかはアプリによって変わってくるトコロ

```csharp
public IEnumerator Save (string session, object visibleByTheUser, object visibleByFriends, object visibleByRegisteredUsers, object visibleByAnonymousUsers)
{
	string url = "http://localhost:9000/me";
	using (UnityWebRequest request = UnityWebRequest.Put (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("Content-type", "application/json");
		request.SetRequestHeader ("X-BB-SESSION", session);

		// リクエストボディ
		IDictionary payloadDictionary = new Dictionary<string, object> ();
		payloadDictionary.Add ("visibleByTheUser", visibleByTheUser);
		payloadDictionary.Add ("visibleByFriends", visibleByFriends);
		payloadDictionary.Add ("visibleByRegisteredUsers", visibleByRegisteredUsers);
		payloadDictionary.Add ("visibleByAnonymousUsers", visibleByAnonymousUsers);
		string bodyData = Json.Serialize (payloadDictionary);
		UploadHandler uploadHandler = new UploadHandlerRaw (System.Text.Encoding.UTF8.GetBytes (bodyData));
		request.uploadHandler = uploadHandler;

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```
※引数はJSON

# ユーザ情報の取得
ユーザIDを指定してユーザ情報の取得を行う
ログインユーザからみた相手の情報が取得できる。つまりフレンドだったりフレンドじゃなかったりでvisibleXXXを取得できるできないが変わる

```csharp
public IEnumerator Fetch (string session)
{
	string url = "http://localhost:9000/user/user1";
	using (UnityWebRequest request = UnityWebRequest.Get (url)) {
		// ヘッダ
		request.SetRequestHeader ("X-BB-SESSION", session);

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# 全ユーザ情報の取得
全ユーザの情報を取得する。
条件指定、並べ替え、ページングも可能だがかなり膨大な説明になるので今回は省略する

```csharp
private IEnumerator FetchAll (string session)
{
	string url = "http://localhost:9000/users";
	using (UnityWebRequest request = UnityWebRequest.Get (url)) {
		// ヘッダ
		request.SetRequestHeader (BaasBoxConsts.HeaderSession, session);

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# パスワードの変更
ログインユーザの古いパスワードと新しいパスワードを与える
これまでのセッションが無効化されるので、ユーザにログインをさせよう

```csharp
private IEnumerator ChangePassword (string oldPassword, string newPassword, string session)
{
	string url = "http://localhost:9000/me/password";
	using (UnityWebRequest request = UnityWebRequest.Put (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("Content-type", "application/json");
		request.SetRequestHeader ("X-BB-SESSION", session);

		// リクエストボディ("new"が使えないので手動シリアライズ)
		IDictionary<string,string> requestBody = new Dictionary<string,string> ();
		requestBody.Add ("old", oldPassword);
		requestBody.Add ("new", newPassword);
		string bodyData = Json.Serialize (requestBody);
		UploadHandler uploadHandler = new UploadHandlerRaw (System.Text.Encoding.UTF8.GetBytes (bodyData));
		request.uploadHandler = uploadHandler;

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# ユーザ名の変更
ユーザ名を変更する。既に存在するユーザ名は使えない
これまでのセッションが無効化されるので、ユーザにログインをさせよう

```csharp
private IEnumerator ChangeUserName (string newUserName, string session)
{
	string url = "http://localhost:9000/me/username";
	using (UnityWebRequest request = UnityWebRequest.Put (url, " ")) {
		// ヘッダ
		request.SetRequestHeader ("Content-type", "application/json");
		request.SetRequestHeader ("X-BB-SESSION", session);

		// リクエストボディ
		IDictionary<string,string> requestBody = new Dictionary<string,string> ();
		requestBody.Add ("username", newUserName);
		string bodyData = Json.Serialize (requestBody);
		UploadHandler uploadHandler = new UploadHandlerRaw (System.Text.Encoding.UTF8.GetBytes (bodyData));
		request.uploadHandler = uploadHandler;

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# パスワードリセット
ログインしていない（できない）ユーザのパスワードをリセットする
visibleByTheUser内のemailフィールドに有効なメールアドレスが必須
ユーザにリンク付きメールが送られるので、そのメール内のリンク先でパスワードを変更する

```csharp
private IEnumerator ResetPassword ()
{
	string url = "http://localhost:9000/user/user1/password/reset";
	using (UnityWebRequest request = UnityWebRequest.Get (url)) {
		// ヘッダ
		request.SetRequestHeader (BaasBoxConsts.HeaderAppCode, BaasBox.Instance.AppCode);

		// 通信開始
		yield return request.Send ();

		// 通信結果
		string json = request.downloadHandler.text;
		if (request.responseCode == 200) {
			Debug.Log ("Success >>" + json);
		} else {
			Debug.Log ("Error >>" + json);
		}
	}
}
```

# 気になったこと
## ユーザ名の大文字小文字
ログイン時にユーザ名の大文字・小文字の違いは判別されない
例えばUSER1で登録するとuser1でもUser1でもログイン可。レスポンスのJSONはUSER1のまま

## ユーザの削除はないの？
1.0.0-M3には入るみたい
[Allow to delete users via api](https://github.com/baasbox/baasbox/issues/149)

## メールアドレスからリセットできない？
基本機能にはないのでプラグインで対応する。
[Password recovery by email instead of username](https://groups.google.com/forum/m/#!topic/baasbox/FOAF8n31dv4)
参考：プラグインの作り方
[Plugin-Engine](https://github.com/baasbox/baasbox/wiki/Plugin-Engine)

## ユーザのメールアドレスを確認
現状できない
[Optional email confirmation when user registers](https://github.com/baasbox/baasbox/issues/686)

## パスワードリセットに利用するメールアドレスの設定
設定はコンソールから
![002.png](https://qiita-image-store.s3.amazonaws.com/0/109724/afc3f26a-307c-06b9-a402-d54bb53d8d62.png)

Gmail(Google Apps含む)を使う場合はこのあたりを参考にする
[プリンタ、スキャナ、アプリケーションからメールを送信するための Google Apps の SMTP 設定](https://support.google.com/a/answer/176600?hl=ja)
[SMTP リレーサービスの設定](https://support.google.com/a/answer/2956491)

その他詳細は下記リンクを参考
[Use Cases - Password Recovery with Gmail account](http://www.baasbox.com/documentation/?shell#password-recovery-with-gmail-account)
[[BaasBox] Password recovery](https://groups.google.com/forum/#!searchin/baasbox/Password$20Reset/baasbox/_bNB2LbQWmE/3hnTRNdxh-wJ)

# おわりに
##UnityWebRequestについて
今回の本題ではないが、POSTとかPUTの第2引数が上手く動かなかった。UploadHandlerを使わないとうまくいかないのでその辺りは今後変わりそう。

## BaasBoxのユーザ管理について
まだ正式版でない分、痒いところに手が届かないといった様子。エラー処理に関しても細かく分類はできなさそうな点もちょっと気がかり。
ただ、海外ツールにありがちな日本語使えない問題も特になさそうだった。
執筆時点でこの部分を詳しく解説している記事があまりないのでもっと流行ってほしいなという期待も込めた記事となった。
あとは、今後の機能追加に期待したい