ちょっと試しに申請書を全部消してみる

この記事は [コラボフロー Advent Calendar 2022](https://qiita.com/advent-calendar/2022/collaboflow) 14 日目の記事です！

https://qiita.com/advent-calendar/2022/collaboflow

コラボフローってなんぞや？って方は [こちらのページ](https://www.collabo-style.co.jp/about/) をご参照ください！

## 申請書の削除

コラボフローでは、システム管理者のみ [申請書の削除](https://collaboflow.zendesk.com/hc/ja/articles/115000338064) が可能です。

申請書削除画面の一覧に表示された申請書を一括で削除することができます。

表示件数は最大で 100 件です。

つまり、一括削除の上限は 100 件ということになります。

**「いやー、全部消したいんよ」** って方には、ちょっと物足りないですよね(?)

そこで、コラボフロー REST API が活躍するわけです😎

## ちょっと試しに申請書を全部消してみる

:::note warn
削除した申請書を元に戻すことは出来ません。
全ユーザーの一覧からも削除され、検索もできなくなりますのでご注意ください。
:::

コラボフロー REST API でも申請書の削除ができることはご存知でしょうか？

申請書の削除ができるエンドポイントは `[DELETE] ​/v1​/documents​/{document_id}` です。

API に関するドキュメントは [こちらのリファレンス](http://docs.collaboflow.com/api-docs/#/Document/deleteDocument) をご確認ください。

### Rust を使って実装する

今回は、自分の推し言語である Rust を使って実装してみます。 

https://github.com/codemountains/collaboflow-balus

#### Cargo.toml

Cargo.tomlに必要なライブラリを定義し、インポートします。

```toml:Cargo.toml
[dependencies]
anyhow = "1.0.66"
collaboflow-rs = "0.0.11"
dotenv = "0.15.0"
serde_json = "1.0.89"
tokio = "1.23.0"
```

※ Node.js、npm で言うところの package.json だと思ってもらえればOKです。

#### .env

REST API 実行に必要な情報であるユーザー ID や API キーは外部に漏らせない重要な情報です。

それらの情報はルートディレクトリ上に配置した `.env` に定義して、実行時に読み取ります。

```plaintext:.env
BASE_URL="https://{collaboflow url}/{instance name}/api/index.cfm"
USER_ID="admin user id"
API_KEY="api key"
```

#### main.rs

申請書を全部消す処理の流れは以下の通りです。

1. **検索条件なし**で申請書の検索をおこなう（検索結果の上限は 100 件）
1. 検索結果から申請書の `document_id` を取得
1. 申請書の削除をおこなう

削除した申請書は検索できなくなるので、これを検索結果が 0 件になるまで繰り返し実行します。

申請書の数が多いことを想定し、繰り返し処理中に待機する時間を入れています。

```rust:main.rs
use collaboflow_rs::request::document::documents_search::PostDocumentsSearchRequest;
use collaboflow_rs::{Authorization, CollaboflowClient, Query};
use dotenv::dotenv;
use serde_json::json;
use std::env;

const BASE_URL: &str = "BASE_URL";
const USER_ID: &str = "USER_ID";
const API_KEY: &str = "API_KEY";

const APP_CD: i32 = 1;
const LIMIT: i32 = 100;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    println!("申請書の削除を開始します...");

    // .env から REST API 実行に必要な情報を取得する
    let secret = init();

    // API KEY 認証を利用する
    let authorization = Authorization::with_api_key(&secret.user_id, &secret.api_key);

    // クライアントを生成する
    let client = CollaboflowClient::new(&secret.base_url, authorization);

    // 削除した申請書をカウントする
    let mut deleted = 0;

    loop {
        // 検索条件を生成する
        let request =
            PostDocumentsSearchRequest::new(json!({"app_cd": APP_CD, "offset": 0, "limit": LIMIT}));

        // 申請書検索を実行する
        let resp = client.documents_search.post(request).await?;
        
        // 申請書一覧を取得
        let documents = resp.body.records;

        // 申請書の検索結果が 0 件の場合はループを抜ける
        if documents.is_empty() {
            break;
        }

        // 申請書を順番に削除する
        for document in documents {
            // 削除 API では app_cd の指定が必要
            let query = Query::builder().app_cd(1);

            // 削除を実行する
            let _ = client.document.delete(document.document_id, query).await?;
            deleted += 1;

            // 待機する
            tokio::time::sleep(tokio::time::Duration::from_millis(3000)).await;
        }
    }

    println!("{} 件の申請書を削除しました \\(°∀° )/", deleted);

    Ok(())
}

struct Secret {
    base_url: String,
    user_id: String,
    api_key: String,
}

fn init() -> Secret {
    dotenv().ok();

    let base_url = env::var(BASE_URL).unwrap_or_else(|_| panic!("{} is undefined.", BASE_URL));
    let user_id = env::var(USER_ID).unwrap_or_else(|_| panic!("{} is undefined.", USER_ID));
    let api_key = env::var(API_KEY).unwrap_or_else(|_| panic!("{} is undefined.", API_KEY));

    Secret {
        base_url,
        user_id,
        api_key,
    }
}
```

### いざ、 実行

:::note alert
大事なことなので、もう一度言います。
**削除した申請書を元に戻すことは出来ません。**
:::

ここに申請書が 777 件あります。

![collaboflow_docs_delete_777.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641252/80c6703d-5f49-4b64-8bdc-21ed9060008a.jpeg)

覚悟はいいですか？

**ちょっと試しに全部消してみます。**

```
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.47s
     Running `target/debug/collaboflow-balus`
申請書の削除を開始します...
777 件の申請書を削除しました \(°∀° )/
```

やっちまったぜ...！

全ての申請書を跡形もなく消し去ることができました🥳🍻

![collaboflow_docs_deleted.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641252/4069a01b-5ee9-44e2-9386-85b08e1c4e47.jpeg)


## コラボフロー REST API クライアント ライブラリを作った

さて、上で紹介した `main.rs` で気になる部分はありましたか？

ありましたよね？

こことか...

```rust
let client = CollaboflowClient::new(&secret.base_url, authorization);
```

こことか...

```rust
let resp = client.documents_search.post(request).await?;
```

こことか...！

```rust
let _ = client.document.delete(document.document_id, query).await?;
```

そうです、

**Rust で コラボフロー REST API クライアント ライブラリを作ってみました！**

必要なライブラリとして、しれっとインポートしていました。

```toml:Cargo.toml
collaboflow-rs = "0.0.11"
```

npm や pip で `collaboflow` と検索してもヒットしません。

おそらく、コラボフロー史上初のライブラリではないでしょうか！

### collaboflow-rs

**collaboflow-rs** は Rust のセントラルパッケージリポジトリである [crates.io](https://crates.io/crates/collaboflow-rs) で公開しています。

https://crates.io/crates/collaboflow-rs

![crate_collaboflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641252/1e5aacd8-4718-1ca5-380a-30109dbf32e0.png)

ソースコードが気になる方は Github を見てみてください。

https://github.com/codemountains/collaboflow-rs

### 使い方

:::note info
v0.0.11 での実装例です。
:::


#### Cargo.toml

非同期ランタイムである [tokio](https://crates.io/crates/tokio) も合わせて使用します。

```toml:Cargo.toml
[dependencies]
collaboflow-rs = "0.0.11"
tokio = "1.23.0"
```

#### 認証情報の生成

コラボフロー REST API では、API キーとパスワードを使用した 2 種類の認証方法があります。

BASE64 でエンコードだなんだって考えることなく認証情報を生成できます！

```rust
use collaboflow_rs::Authorization;

// API キー
let authorization = Authorization::with_api_key("User id", "API key");

// パスワード
let authorization = Authorization::with_password("User id", "Password");
```

#### Collaboflow クライアントの生成

[reqwest](https://crates.io/crates/reqwest) という HTTP クライアント ライブラリをラップしています。

REST API の URL と生成した認証情報で簡単にクライアントを生成できます。

URL の形式は `https://{collaboflow url}/{instance name}/api/index.cfm` です。

```rust
use collaboflow_rs::CollaboflowClient;

let client = CollaboflowClient::new("REST API URL", authorization);
```

#### 🔎 申請書の検索

エンドポイントは `[POST] /v1/documents/search` です。

collaboflow-balus でも使用しました。

```rust
let resp = client.documents_search.post(request).await?;
```

#### ❌ 申請書の削除

エンドポイントは `[DELETE] /v1​/documents​/{document_id}` です。

collaboflow-balus の核となる処理です。

```rust
let _ = client.document.delete(document.document_id, query).await?;
```

#### 📝 申請書の申請

エンドポイントは `[POST] /v1/documents` です。

もちろん、削除するためだけに申請した 777 件の申請書もライブラリを活用して申請しています。

700 件も画面から申請なんてできないですからね😌笑

```toml:Cargo.toml
[dependencies]
collaboflow-rs = "0.0.11"
tokio = "1.22.0"
serde_json = "1.0.89"
anyhow = "1.0.66"
```

```rust:main.rs
use collaboflow_rs::request::document::documents::PostDocumentsRequest;
use collaboflow_rs::{Authorization, CollaboflowClient};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // パスワード認証
    let authorization = Authorization::with_password("User id", "Password");
    
    // クライアントを生成
    let client = CollaboflowClient::new(
        "Collaboflow REST API URL",
        authorization,
    );

    // テキストパーツ fid1 に文字を登録
    let document = json!({"fid1": "削除待ち"});

    // 経路ID: 4, アプリCD: 1, 文書タイトルを指定
    let request = PostDocumentsRequest::new(
        4,
        None,
        None,
        None,
        1,
        Some("ただ削除されるのを待つ申請書"),
        document,
    );

    // 申請書の申請を実行
    let resp = client.documents.post(request).await?;

    // 文書番号を出力
    println!("No. {}", resp.body.document_number);

    Ok(())
}
```

### ドキュメント

もっと他の API を触ってみたい方は以下のドキュメントをご確認ください。

https://docs.rs/collaboflow-rs/

### Rust で外部連携プログラムを開発

Windows 環境であれば、ビルドして exe ファイルにしてタスクスケジューラなどで定期実行することも可能です。

たぶん、いろいろなことが **理論上は実装可能** です😘

ぜひ、コラボフローの外部連携プログラムの作成に Rust を採用してみてください！

### collaboflow-rs について

[Github の README.md](https://github.com/codemountains/collaboflow-rs/blob/main/README.md) にも記載の通り、公式のライブラリではありません。

> This is not an official crate, just a hobby project.

あくまで、趣味で作成したライブラリです。

一部 API やクエリパラメータには非対応です。

また、バージョンも v1 まで到達しておらず、
今後のバージョンアップで破壊的な修正があるかもしれません...(笑)

というわけで、本ライブラリの使用は自己責任でお願いします。MIT ライセンスですしね😉

## さいごに

**くれぐれも申請書の削除は慎重に。**

今回、Rust でライブラリを作成しましたが、需要が多いのは npm パッケージでしょうね(笑)

npm パッケージ版、誰かはよー！

もたもたしてると俺が作っちゃうぞ？

明日は @nana_csx さんです🫰🌟