Unityで NCMB ニフティクラウド mobile backendを使うメモ

#ニフティクラウド mobile backendとは

ニフティクラウド mobile backend（以降NCMB）とは、ニフティさんが提供するmBaaSサービスです。
様々なプラットフォームで利用可能であり、UnityにもSDKが提供されています

このNCMB、サービス自体は素晴らしいのですが、 **ドキュメントとUnity pluginの使い勝手がクソ** です。
自分が踏んだ罠を、共有も兼ねてメモしていきたいと思います。

#メモ
## ログイン/サインイン処理をObservable化する

\*Asyncシリーズのコールバック引数が **気持ち悪くてありえない** 感じになっているので、それを隠蔽してUniRxのIObservableにしました。
別記事にまとめたのでこちらを参考に。

[【NCMB】 ニフティクラウド mobile backend のログイン処理をIObservable化してUniRxで扱えるようにする](http://qiita.com/toRisouP/items/e101cb50835285b0fe6c)

##ユーザがメールアドレスの確認を行っているか調べる

設定でメールアドレス認証をONにしていて、かつ確認メールをちゃんとユーザが確認したかアプリから取得する方法を調べました。

```csharp:メールアドレスの確認を行っているか
//ログイン
ObservableNcmbUserAuth.LoginAsync(id, pass)
    .Subscribe(user =>
    {
       //未確認の場合はnull、確認済みの場合はtrue
       var isConfirmed = user["mailAddressConfirm"];
       //未確認の場合はnull、確認済みの場合はメールアドレス
       var mailAddress = user.Email;
    });
```

##ユーザのメールアドレスを再設定する

シンプルにEmail属性を書き換えてSaveすればOK.
アプリケーション設定から確認メールを有効にしていた場合、自動的に確認メールが送信されます。

```csharp:メールアドレスを再設定する
//userはNCMBUser
user.Email = "newmailaddress@hogehoge.fuga";
user.SaveAsync();
```


## 新規ユーザ登録時に `Authentication error by header incorrect` というエラーが出てしまう

結論からいうと、「ログアウト」すれば直ります。
デバッグ時に発生しがちなエラー。アプリからユーザ登録した後に、そのユーザを管理コンソールから削除すると発生します。
理由は前のユーザのセッション情報がアプリに残ってしまっており、それでAPIリクエストを行ってしまうから。
[参考](https://github.com/NIFTYCloud-mbaas/UserCommunity/issues/9)

```csharp:対応策
void Start()
{
    //起動直後にとりあえずログアウトを実行しておけば回避可能
    NCMBUser.LogOutAsync();
}
```

## 既存の会員ロールにユーザを追加する

~~これは[APIドキュメント](http://mb.cloud.nifty.com/doc/current/user/manage_privileges_unity.html#会員をロールに追加する)が間違っていて非常にたちが悪いです。~~ **ドキュメントが修正対応されました**
APIドキュメントの方法で実行すると、「ロールの新規追加処理」が走ってしまうため、`roleName is duplication.`といったエラーが発生してしまいます。

ではどうするのかというと、NCMBRoleをnewを使って取得するのではなく、サーバから取得してあげなくてはいけません。

```csharp:サーバからロール一覧を取得する
NCMBRole.GetQuery().FindAsync((roleList, error) =>
{
    //roleListの型はList<NCMBRole>
    Debug.Log(roleList.Count);
});
```

```csharp:ロール名を指定してユーザを追加する場合
//"proPlan"というロールにuserを追加する
NCMBRole.GetQuery().WhereEqualTo("roleName", "proPlan").FindAsync((roleList, error) =>
{
    var role = roleList.FirstOrDefault();
    role.Users.Add(user);
    role.SaveAsync();
});
```

`NCMBRole.GetQuery()`を使えばObjectIdが設定されたNCMBRoleが取得できるので、これを使ってあげれはユーザの追加を行うことができます。

## 会員ロールにユーザを追加すると`Operation is invalid after previous operation.`が発生する

これは **UnitySDKのバグ** で、該当ロールのレコードが空の場合に発生します。適当にユーザを作ってを該当のロールに入れておけば回避できます。~~マジかよ。~~

## ACLの設定方法
オブジェクトの権限（ACL)を変更する場合、NCMBObjectのACLを一度取得し、 **変更した後に再代入** をしてあげる必要があります。

```csharp:ACLの上書き
var acl = user.ACL;
//adminにread権限を追加する
acl.SetRoleReadAccess("admin", true);
user.ACL = acl; //この操作が必要
user.SaveAsync();
```

理由としては、Save処理はNCMBObjcetの内部状態が変化した時にしか実行されないためです。
そのため、再代入を明示的に行って値が変更したことをNCMBObjectに認識させてあげる必要があります。

#### ちなみにREST APIで踏んだ罠

権限を不許可にする場合は要素を未指定にしなくてはいけない。

```rb:権限を不許可にする場合の注意点
# Read/Write両方を許可したい場合はtrueを指定
'role:Admin' => {
  'write' => true,
  'read' => true
}
​
# じゃあread onlyしようと思ってfalseを指定したらエラー
'role:Admin' => {
  'write' => false,
  'read' => true
}
​
# ReadOnlyにしたい場合、その要素を指定しないのが正しい
'role:Admin' => {
  'read' => true
}
```

# パスワードの再設定を行う

`NCMBUser.RequestPasswordResetAsync(string email)` を実行することで、対象のメールアドレスにパスワードリセットメールが送信できる。パスワードの再設定はメールアドレスに記載されたURLにあるForm上で行うことになる。
**この方法はメールアドレスが登録されたユーザにしか利用できない。**

```csharp:使用例(UniRxで使うためにObservable.Createで包んでいる)
/// <summary>
/// パスワード再設定を行う
/// </summary>
/// <returns>成功時OnNext 失敗時OnError</returns>
public IObservable<Unit> ResetPassword(string email)
{
    return Observable.Create<Unit>(observer =>
        {
            if (string.IsNullOrEmpty(email))
            {
                observer.OnError(new Exception("メールアドレスが設定されていません"));
            }
            else
            {
                NCMBUser.RequestPasswordResetAsync(email, ex =>
                {
                    if (ex != null)
                    {
                        observer.OnError(ex);
                    }
                    else
                    {
                        observer.OnNext(Unit.Default);
                        observer.OnCompleted();
                    }

                });
            }
            return Disposable.Create(() => { });
        });
}
```