DriveAPIでGドラ内ファイルを取得、削除、アップロードする【Node.js】【Express】

Webアプリをデプロイする際に、画像を保存するWebストレージを用意する必要がでてくる。
よく使われるのがAWSやGCPになると思が、いかんせんお金がかかってくる。
そこで、無料で使えてよく馴染みのある__Googleドライブ__を利用できたらなと思って、今回チャレンジしてみた。

#事前準備
以下を事前に行っておくこと。
※手順は割愛

1. Node.js、npmがインストールされている
1. APIが有効になっているGoogleCloudPlatformプロジェクトが作成されていること
1. Googleドライブが有効なGoogleアカウント

#1.認証情報の作成
GCPでOAuth2認証の設定をする
__①「APIとサービス」＞「認証情報」画面の「認証情報を作成」から認証情報を作成する__
![スクリーンショット 2021-12-22 8.19.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547731/2f21d2ee-dbf0-656e-5d91-2a296673913d.png)
__②「OAuthクライアントID」を選択__
![スクリーンショット 2021-12-22 8.20.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547731/f44f4130-e97d-2130-173a-752481a8f2be.png)
__③必要情報を入力して「作成」を押下する__
・アプリケーションの種類：ウェブアプリケーション
・名前：任意の名前
・URI：ローカル開発のため「localhost」を指定
![スクリーンショット 2021-12-22 8.51.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547731/2faefcf8-33f6-3d49-f0a5-0ff6c6019a83.png)

#2.credentials.jsonの作成
OAuth2認証の際に必要となる以下の情報をGCPから取得する。
取得した情報は「credentials.json」とし、アプリケーションディレクトリ配下に配置する。
・client_secret
・client_id
・redirect_uris

__①GCPの「APIとサービス」＞「認証情報」画面から、作成した「OAuthクライアント」をダウンロードする__
![スクリーンショット 2021-12-22 9.08.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547731/a4827cee-4f20-fc35-602a-2688cabc1d1a.png)
__②ダウンロードしたjsonファイルを保存する__
・ファイル名：「credentials.json」
・場所：アプリケーションのトップディレクトリ

__③ファイルの編集__
以下の通りになるように、ファイル内を編集する。

```
{"client_secret":"GOCSPX-eXXXXXXXXXXXXXXXXX","client_id":"9199999999-XXXXXXXXXXXXXXpj2cl.apps.googleusercontent.com","redirect_uris":["http://localhost:3000"]}
```

#app.jsの作成
[公式サイト](https://developers.google.com/drive/api/v3/quickstart/nodejs)のクイックスタートを参考に、app.jsに実装していきます。

```app.js
const express = require('express');
const app = express();
app.use(express.static('public'));

// ****************************************************************************
//   定数
// ****************************************************************************
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

//スコープを追加、削除する場合は、token.jsonを削除してください。
const SCOPES = ['https://www.googleapis.com/auth/drive.file'];

//トークン情報を保持しているファイル名
const TOKEN_PATH = 'token.json';

//Oauth2のJSONファイル名
const OAUTH2 = 'credentials.json'

// ****************************************************************************
//   ルーティング
// ****************************************************************************
app.get('/', (req, res) => {
    fs.readFile(OAUTH2, (err, content) => {
        if (err) return console.log('Error loading client secret file:', err);
        //Gドラ内のファイルを取得する
        authorize(JSON.parse(content), listFiles);
      });
    
    res.render('hello.ejs');
});

app.post('/upload',(req, res) => {
    fs.readFile(OAUTH2, (err, content) => {
        if (err) return console.log('Error loading client secret file:', err);
        //Gドラにファイルをアップロードする
        authorize(JSON.parse(content), UploadFiles);
    });
 
    res.redirect('/');
});

app.post('/delete', (req, res) => {
    fs.readFile(OAUTH2, (err, content) => {
        if (err) return console.log('Error loading client secret file:', err);
        //Gドラ内のファイルを削除する
        authorize(JSON.parse(content), DeleteFiles);
    });
 
    res.redirect('/');
});

// ****************************************************************************
//   Drive api
// ****************************************************************************
//指定された資格情報を使用してOAuth2クライアントを作成し、指定されたコールバック関数を実行します。
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials;
  const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);

  // トークン情報を取得できない場合は再度取得する
  // トークン情報取を得できた場合は処理の実行
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getAccessToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

// アクセストークンの取得。「token.json」として保存する
// 認証されたOAuth2クライアントで指定されたコールバックを実行
function getAccessToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // 後でプログラムを実行できるように、トークンをディスクに保存
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

//Gドラ内のファイル名、IDを最大10個まで取得する
function listFiles(auth) {
  const drive = google.drive({version: 'v3', auth});
  drive.files.list({
    pageSize: 10,
    fields: 'nextPageToken, files(id, name)',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const files = res.data.files;
    if (files.length) {
      console.log('Files:');
      files.map((file) => {
        console.log(`${file.name} (${file.id})`);
      });
    } else {
      console.log('No files found.');
    }
  });
}

//Gドラにファイルをアップロードする
function UploadFiles(auth) {
    const drive = google.drive({version: 'v3', auth});  
    var fileMetadata = {
        name: 'UploadFileName.jpg', //アップロード後のファイル名
        parents: ['1Q44YS2E0XXXXXXXXXXXXXXXXXXXXXX-'] //アップロードしたいディレクトリID
    };
    var media = {
        mimeType: 'image/jpeg', //アップロードファイル形式
        body: fs.createReadStream('img/test.jpg') //アップロードファイル名(img配下のtest.jpg)
    };

    drive.files.create({
        resource: fileMetadata,
        media: media,
        fields: 'id'
      }, function (err, file) {
    if (err) {
        console.error(err);
    } else {
        console.log('File Id: ', file.data.id);  
    }
    });
}

//Gドラ内の任意のファイルを削除する
function DeleteFiles(auth) {
  const drive = google.drive({version: 'v3', auth});  
  const deleteFileId = '1uC2ViXXXXXXXXXXXXXXXXXX'; //削除したいファイルID

  const params = {
    fileId: deleteFileId
  };

  drive.files.delete(
    params,
    function (err, res) {
      if (err) {
        console.error(err);
      } else {
        console.log('resDel :', res);  
      }
    }
  );
}

app.listen(3000);
```

####動作の確認
今回は以下URIでのファイル操作としました。
　　Gドラ内のファイル情報取得　＝　「localhost:3000」に接続
　　Gドラにファイルをアップロード　＝　「localhost:3000/upload」に接続
　　Gドラ内のファイルを削除　＝　「localhost:3000/upload」に接続

__アクセストークンの取得__
初回起動時に、認証フローを行いアクセストークンを取得します。
取得したトークン情報は、app.jsと同階層下に`token.json`として保存されます。
トークンの有効期限切れ、またはtoken.jsonがない場合は、再度認証フローを行いアクセストークンを取得します。

__スコープ__
スコープとは、Google Driveを操作できる範囲のことです。
指定することにより許可された範囲でGoogle Driveの操作ができます。
「https://www.googleapis.com/auth/drive.file」
　・対象ファイルの参照
　・対象ファイルのアップロード、ダウンロード
　・対象ファイルの削除
　・対象ファイルを共有しているユーザーの名前、メールアドレスの参照
　・他のユーザーとの対象ファイルの共有、共有の停止
　・対象ファイルの整理
「https://www.googleapis.com/auth/drive」
　・全ての捜査権限付与

※スコープ範囲を変更する場合は、`token.json`を削除し、アクセストークンを取得し直してください

#認証フロー
初回起動時に、認証フローを行いアクセストークンを取得する。
__①アプリを実行する__

```
$ node app.js
```

__②表示されたURLをブラウザに貼り付ける__

```
$ node app.js

Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&response_type=code&client_id=91599999999-oq9XXXXXXXXXXXXXXXXrsmnplpj2cl.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A3000
```

__③認証後、URLに表示された認証コードをターミナルに貼り付け、認証を完了する__
1.ブラウザのURLから取得（code=より右）
`URL:「http://localhost:3000/?code=4/0AX4XfWiYB3seKkZO5dGNpT_Oq4HOw0sWda_lPlsX4QUnOWYuVEk2QkOlLII_NEKUp_TSRg&scope=https://www.googleapis.com/auth/drive.metadata.readonly」`

2.ターミナルに貼り付ける

```
$ node app.js

Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&response_type=code&client_id=91599999999-oq9XXXXXXXXXXXXXXXXrsmnplpj2cl.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A3000

Enter the code from that page here:4/0AX4XfXXXXXXXXXXXXXXXXXXXXXXXXXXXYuVEk2QkOlLII_NEKUp_TSRg&scope=https://www.googleapis.com/auth/drive.metadata.readonly
```

3.Enterを押下する

#おわりに
今回は公式サイトを参考にGドラを操作できるようにしてみました。
公式サイトの通りに実装しただけではうまくいかないことが多々あり大変でしたが、コードの動きやDriveAPIとの連携の流れがわかればなんとかなるかなといった感じです。
ベタ打ちとなっていたファイル情報を、ボディパラメータで指定できるようにすれば、Webアプリからファイルをアップロードできるようになるかと思います。