画像をあるアクションでアップロードする

## 内容
javascript、今回はkintoneを使用し、画像をあるサーバからアップする方法を記載する。
以下のパターンを検証する。
1.外部urlより取得
2.kintone内の画像より取得


## 外部urlより取得
### 主な関数、API
・あるURLから画像を取得したい場合fetchを使用する。

https://www.yoheim.net/blog.php?q=20180901

・kintoneでファイルをアップロードする。
https://sample.cybozu.com/k/v1/file.json

https://cybozu.dev/ja/kintone/docs/rest-api/files/upload-file/

kintoneでは以下のようになる。
プロセスのアクションボタンを押下したときに発動させる例。
```javascript
kintone.events.on('app.record.detail.process.proceed', async function(event) {
    if (event.action.value === '画像アップロード') { // アクションボタンの名前に合わせて変更
        try {
            const response = await fetch('外部サーバの画像URL');
            const blob = await response.blob();

            // kintoneのAPIを使ってblobをアップロード
            let formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); // filenameは実際のファイル名に変更

            const headers = {
                'X-Cybozu-API-Token': 'YOUR_API_TOKEN' // ここに実際のAPIトークンを設定してください
            };

            //画像をアップロード
            const uploadResp = await fetch("/k/v1/file.json", {
                method: "POST",
                headers: headers,
                body: formData,
            });
            
            const uploadData = await uploadResp.json();
            
            if (uploadData.fileKey) {
                record[attachmentFieldCode].value.push({ fileKey: uploadData.fileKey });
            }


        } catch (error) {
            console.error('Error:', error);
        }
    }
    return event;
});
```
画像の取得元のサーバがCORS（Cross-Origin Resource Sharing）をサポートしている必要がある。CORSの設定がされていないと、ブラウザのセキュリティポリシーにより画像の取得に失敗する可能性あり。
kintoneのAPIの呼び出しに関するレートリミットや、ファイルのサイズ制限なども考慮する必要あり。


上記はasync awaitを使用しているが下記はfetchとthenを使ったPromiseペースを使用した例。
```javascript
kintone.events.on('app.record.detail.process.proceed', function(event) {
    if (event.action.value === '画像アップロード') { 
        fetch('外部サーバの画像URL')
        .then(response => response.blob())
        .then(blob => {
            var formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); 

            return kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {
                fileKey: formData
            });
        })
        .then(response => {
            if (response.fileKey) {
                event.record['添付ファイルフィールドのAPIコード'].value = [response.fileKey];
            }
        })
        .catch(error => {
            console.error('Error:', error);
        });
    }
    return event;
});


```

あと上記においてエラーハンドリングは強化しておくべき。
```javascript
kintone.events.on('app.record.detail.process.proceed', function(event) {
    if (event.action.value === '画像アップロード') {
        fetch('外部サーバの画像URL')
        .then(response => {
            if (!response.ok) {
                throw new Error('外部サーバからの画像の取得に失敗しました。');
            }
            return response.blob();
        })
        .then(blob => {
            var formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); 

            return kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {
                fileKey: formData
            });
        })
        .then(response => {
            if (!response.fileKey) {
                throw new Error('kintoneへの画像のアップロードに失敗しました。');
            }
            event.record['添付ファイルフィールドのAPIコード'].value = [response.fileKey];
        })
        .catch(error => {
            console.error('Error:', error);
            alert(error.message);  // ユーザへのエラーメッセージの表示
            // ここで、エラーログの保存やリトライの機構を実装することも考えられます。
        });
    }
    return event;
});

```

## kitone内の画像で対応
外部urlより取得の場合、CORS問題で取得できない場合がある。
その場合は無理に外部urlで行うよりも、kintone内のアプリより画像を取得してセットとした方が早い。

https://cybozu.dev/ja/kintone/docs/rest-api/files/download-file/

以下の流れで対応できる。
既存レコードに対してカスタマイズで画像のアップロードが可能
1.JavaScriptカスタマイズでボタン追加
2.カスタマイズボタンクリック処理
3.kintoneのあるアプリ(ファイル管理)より画像を取得
4.取得した画像のアップロード
5.ファイルキーを取得し該当レコードを更新

1. **JavaScriptカスタマイズでボタン追加**
   - kintoneのアプリカスタマイズ機能を使用して、アプリの詳細画面にカスタムボタンを追加します。
   - 例: `kintone.app.record.getHeaderMenuSpaceElement().appendChild(button);`

2. **カスタマイズボタンクリック処理**
   - ボタンにクリックイベントを追加し、処理を定義します。
   - 例: `button.onclick = function() { // 以下の処理を実行 };`

3. **kintoneのあるアプリ（ファイル管理）より画像を取得**
   - kintone REST APIを使用して、特定のアプリから画像ファイルを取得します。
   - 例: `kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: <ファイル管理アプリID>, id: <レコードID>}, function(resp) { // 画像データの取得 });`

4. **取得した画像のアップロード**
   - 取得した画像ファイルを、アップロード用のAPIを使用してkintoneにアップロードします。
   - 例: `kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {fileKey: <取得した画像のファイルキー>}, function(resp) { // アップロード完了後の処理 });`

5. **ファイルキーを取得し該当レコードを更新**
   - アップロードが完了したら、ファイルキーを取得します。
   - 取得したファイルキーを使用して、対象のレコードを更新します。
   - 例: `var body = {app: <アプリID>, id: <レコードID>, record: {<画像フィールド名>: {value: [<ファイルキー>]}}}; kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body, function(resp) { // レコード更新完了後の処理 });`

これらのステップを組み合わせることで、kintoneのアプリにおいて既存レコードに対して画像のアップロードを実現できます。APIの詳細やパラメータは、kintoneのドキュメントを参照して適宜調整してください。





以下は間違っています！、間違い箇所は保存ボタン押下では、画像をセットできません！
1.画像の取得
2.取得した画像のアップロード
3.アップロードした結果として得られる新しいfileKeyをレコードの添付ファイルフィールドにセット
4.その変更をeventオブジェクトに反映
5.return event;でkintoneの標準の新規登録・更新の動作を続行
```javascript
async function getAndUploadAttachment(fileKey) {
    const headers = {
        'X-Requested-With': 'XMLHttpRequest',
    };
    try {
        // 画像を取得
        const resp = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
            method: 'GET',
            headers,
        });
        const blob = await resp.blob();
        
        // 新しい添付ファイルとして登録
        const formData = new FormData();
        formData.append("file", blob);
        
        const uploadResp = await fetch("/k/v1/file.json", {
            method: "POST",
            headers,
            body: formData,
        });
        
        const uploadData = await uploadResp.json();
        return uploadData.fileKey;
    } catch (error) {
        console.error("Error:", error);
        throw error;
    }
}

//新規、編集時の保存ボタン押下
kintone.events.on('app.record.create.submit,app.record.edit.submit', async function(event) {
    const record = event.record;
    const attachmentFieldCode = 'YOUR_ATTACHMENT_FIELD_CODE';

    record[attachmentFieldCode].value[0].fileKey = await getAndUploadAttachment(fileKey);

    if (event.type === 'app.record.create.submit') {
        // 新規作成時のみの処理
    } else if (event.type === 'app.record.edit.submit') {
        // 編集時のみの処理
    }

    return event;
});
```
### メモ
もう少し具体的に記載
個別関数のエラーハンドリング
```javascript
async function getImageFileKey(imageAppId, imageRecordId) {
  try {
    const imageResp = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {
      app: imageAppId,
      id: imageRecordId
    });
    return imageResp.record['添付ファイルフィールドコード'].value[0].fileKey;
  } catch (err) {
    console.error('画像のファイルキー取得でエラーが発生しました:', err);
    throw new Error('画像のファイルキー取得に失敗しました。');
  }
}

async function downloadImage(fileKey) {
  try {
    const fileResp = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
      method: 'GET',
      headers: { 'X-Requested-With': 'XMLHttpRequest' }
    });
    if (!fileResp.ok) {
      throw new Error('ファイルのダウンロードに失敗しました。');
    }
    return await fileResp.blob();
  } catch (err) {
    console.error('画像のダウンロードでエラーが発生しました:', err);
    throw err; // 再スローして呼び出し元でキャッチする
  }
}

async function updateRecordWithImage(currentAppId, currentRecordId, blob) {
  try {
    const formData = new FormData();
    formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
    formData.append('file', blob, 'image.jpg'); // ファイル名は適宜変更可能

    const uploadResp = await kintone.api(kintone.api.url('/k/v1/file', true), 'POST', formData);
    const updateResp = await kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', {
      app: currentAppId,
      id: currentRecordId,
      record: {
        '更新対象の添付ファイルフィールドコード': {
          value: [{ fileKey: uploadResp.fileKey }]
        }
      }
    });
    return updateResp;
  } catch (err) {
    console.error('レコードの更新でエラーが発生しました:', err);
    throw new Error('レコードの更新に失敗しました。');
  }
}

```

メイン関数のエラーハンドリング
```javascript
async function updateImageProcess() {
  try {
    // 各ステップの関数を呼び出し
    const fileKey = await getImageFileKey(画像アプリID, 画像レコードID);
    const blob = await downloadImage(fileKey);
    const updateResp = await updateRecordWithImage(kintone.app.getId(), kintone.app.record.getId(), blob);

    console.log('レコード更新完了', updateResp);
  } catch (err) {
    // 呼び出し元でキャッチされたエラーの処理
    console.error('画像更新プロセスでエラーが発生しました:', err);
    alert(err.message);
  }
}

```

はい、おっしゃる通りです。個別の関数でエラーが発生した場合、`throw new Error` を使用してエラーをスローし、それをメイン関数の`try...catch`ブロックでキャッチするというのが一般的なアプローチです。また、`catch`ブロック内で`throw`を使用してエラーを再スロー（rethrow）することもできます。これにより、エラーの伝播と適切なハンドリングが可能になります。

### エラーの再スロー（Rethrow）

エラーの再スローは、エラーをキャッチして何らかの処理を行った後に、そのエラーを上位のコードに伝播させるために使われます。以下はその一例です：

```javascript
try {
  // 何らかの処理
} catch (err) {
  // エラー処理
  console.error('エラーが発生しました:', err);

  // エラーを再スロー
  throw err;
}
```

このパターンでは、`catch`ブロックがエラーをキャッチし、ログ記録などの処理を行った後、同じエラーを`throw`を使って再スローします。これにより、エラーはさらに上位のコード（例えば、他の`try...catch`ブロックや呼び出し元の関数）に伝播され、そこで適切にハンドリングされることになります。

### throw new Error と throw err の違い

- `throw new Error('メッセージ')`: 新しいエラーオブジェクトを作成してスローします。これは新たなエラーコンテキストを作成し、独自のエラーメッセージを指定する際に使用されます。
- `throw err`: 既にキャッチされたエラーオブジェクトをそのまま再スローします。これは、エラーの伝播を維持しながら、追加の処理（ログ記録など）を行う場合に使用されます。


### throw err

- **使用する場合**: 既にキャッチされたエラーオブジェクトをそのまま上位のコードに伝播させたい場合。
- **目的**: エラーの原因やスタックトレースを変更せずに、そのままエラーを伝播させます。これにより、エラーが最初に発生した場所の情報が保持されます。

### throw new Error

- **使用する場合**: 新しいエラーコンテキストを作成したい、またはエラーメッセージをより明確にしたい場合。
- **目的**: 新しいエラーオブジェクトを作成することで、エラーの説明をカスタマイズできます。ただし、新しいエラーオブジェクトを作成すると、元のエラーのスタックトレースは失われます。

### 使い分けのポイント

- エラーの詳細（原因や場所）をそのまま保持する必要がある場合は`throw err`を使用します。
- エラーの説明をカスタマイズしたい、またはエラーの文脈を変更したい場合は`throw new Error('カスタムメッセージ')`を使用します。

### まとめ

エラーハンドリングの際には、エラーの原因となったコンテキスト、エラーメッセージの明確さ、およびエラー情報の伝播方法を考慮して、`throw err`と`throw new Error`のどちらを使用するかを決定します。これにより、エラーをより適切に処理し、開発者やエンドユーザーに必要な情報を提供できます。