Cloud Functions for Firebaseに学部長をデプロイして呼び出してみた

事の発端は、在学中の大学の学部長の日記。

https://www.sfc.keio.ac.jp/deans_diary/015098.html

以前も[尖った投稿](https://www.sfc.keio.ac.jp/news/014513.html)をして賛否両論を呼んだ脇田学部長が、またよくわからないことをやってくれたぜ！

MySQLでHTTP鯖を建てた先輩や、数式よりTeXの方がわかりやすいという博士課程の方はいたけれど、学部長レベルになると日記がJavaScriptになるようです。(余談ですが、過去には"卒論.jpeg"という伝説がいたらしい...)

そして、Twitter上でも様々な意見が飛び交う結果に...
中でも目に止まったのが、こちら

>学部長関数｜環境情報学部長　脇田 玲 | 慶應義塾大学 湘南藤沢キャンパス（SFC） 
> https://sfc.keio.ac.jp/deans_diary/015098.html

> 関数定義はしてるけど実行していないのでこの学部長は動かない 
> @barley_ural


引用元：https://twitter.com/barley_ural/status/1338669126370217984

たしかに...

実行してない...

いや...まてよ...

**これは...俺たちにこの関数を実行しろということじゃないのか...！？**
**きっとそこに隠されたメッセージがあるはず！！**


ということで、本題です。学部長をデプロイして呼び出してみよう！
(ちなみに「deanって何？デーン?」って思って、Google先生に聞いたら「学部長」という意味でした)


## Cloud Functionsの初期化
まずはサクッと`firebase init`で新規プロジェクトを作りましょう。
いろいろ質問を聞かれますが、適当に答えていきます。

```sh
$ mkdir dean && cd dean
$ firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Volumes/sd/tmp/dean

Before we get started, keep in mind:

  * You are currently outside your home directory

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. Functions: Configure and deploy Cloud Functions

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: Create a new project
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]:
 dean-015098
? What would you like to call your project? (defaults to your project ID) 
✔ Creating Google Cloud Platform project
✔ Adding Firebase resources to Google Cloud Platform project

🎉🎉🎉 Your Firebase project is ready! 🎉🎉🎉

Project information:
   - Project ID: dean-015098
   - Project Name: dean-015098

Firebase console is available at
https://console.firebase.google.com/project/dean-015098/overview
i  Using project dean-015098 (dean-015098)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
✔  Wrote functions/package.json
✔  Wrote functions/index.js
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

> protobufjs@6.10.2 postinstall /Volumes/sd/tmp/dean/functions/node_modules/protobufjs
> node scripts/postinstall

npm notice created a lockfile as package-lock.json. You should commit this file.
added 226 packages from 191 contributors and audited 226 packages in 12.963s

9 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities


i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

✔  Firebase initialization complete!
```

## コードを書く
とりあえず、「おかしら日記」をコピペしてくる。

```diary.js
////////
//FUNCTION OF DEAN
//
//written by Akira Wakita
//Dec.08,2020
//
////////


var counter = 0;

const me = {
    isAlive: true
}


function dean () {

    while(me.isAlive) {

        ////////
        //Usual operations
        ////////

        approveDocuments(ONLINE);

        attendMeetings(ONLINE);
        doSomeLectures(ONLINE);

        gotoCampus(SFC);
        attendMeetings(OFFLINE);
        sanpo(SFC);
        eatLunch(SUBWAY);

        gotoCampus(MITA);
        attendBoardMeetings(OFFLINE);

        conductEntranceExam();
        doPublicRelations();
        doCrisisManagement();

        doOtherManyJobs();

        ////////
        //What can be achieved when there is a chance
        ////////

        if(counter % 20 === 0){
            kaizenAdministrationProcesses();
        }

        if(counter % 30 === 0){
            doOwnResearch();
        }
        counter++;


        ////////
        //Things that are not done until, but must be done.
        ////////

        //reformGraduateSchool();
        //reformRevenueProcess();
        //reformPublicRelations();
    }

}

////////
//Let's go to our campus.
//Talk to your friends in the sunshine, not through Zoom.
//Breathe in the beautiful scenery and clean air of the campus with your body.
//Wash your hands, keep your distance, and have lunch with friends at the Subway in front of the duck pond.
//The campus is sparse right now.
////////

//引用元 https://www.sfc.keio.ac.jp/deans_diary/015098.html

+ exports.dean = dean;
```
スマートな構成にするために、`exports.dean = dean`を加筆しました。
これで`require`で読み込めるようになるので、`index.js`を編集していきましょう。
ついでに未定義な関数や定数も適当に定義していきます。

```index.js
const functions = require('firebase-functions');
const { dean } = require('./diary');

globalThis.ONLINE = 'ONLINE';
globalThis.OFFLINE = 'OFFLINE';

globalThis.SFC = 'SFC';
globalThis.MITA = 'MITA';
globalThis.SUBWAY = 'SUBWAY';

globalThis.approveDocuments = (status) => {};
globalThis.attendMeetings = (status) => {};
globalThis.doSomeLectures = (status) => {};
globalThis.gotoCampus = (campus) => {};
globalThis.sanpo = (campus) => {};
globalThis.eatLunch = (restaurant) => {};
globalThis.attendBoardMeetings = (status) => {};
globalThis.conductEntranceExam = () => {};
globalThis.doPublicRelations = () => {};
globalThis.doCrisisManagement = () => {};
globalThis.doOtherManyJobs = () => {};
globalThis.kaizenAdministrationProcesses = () => {};
globalThis.doOwnResearch = () => {};

exports.dean = functions.region('asia-northeast1').https.onRequest((request, response) => {
	response.status(200).send(dean());
});


```

## デプロイ
あとはデプロイするだけですね！
Firebase、とっても楽ちん！

```sh
$ cd functions
$ npm run deploy

> functions@ deploy /Volumes/sd/tmp/dean/functions
> firebase deploy --only functions


=== Deploying to 'dean-015098'...

i  deploying functions
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
✔  functions: required API cloudfunctions.googleapis.com is enabled
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...

Error: HTTP Error: 400, Billing account for project '320747462128' is not found. Billing must be enabled for activation of service(s) 'cloudbuild.googleapis.com,containerregistry.googleapis.com' to proceed.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! functions@ deploy: `firebase deploy --only functions`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the functions@ deploy script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/nontan/.npm/_logs/2020-12-15T15_44_44_851Z-debug.log

```

と思いきや、課金オプションを有効にしろとエラーが出ます。
(※Cloud Functions for Firebaseを使うにはBlazeプランへの以降が必要)
(※月２００万回の呼び出しまで無料)

## Blazeプランを有効化
なので、コンソールの「Cloud Functions」タブにいって、課金を有効化します。
Firebase コンソール=> https://console.firebase.google.com/u/0/project/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420158/c49f54ef-2cb3-f773-d0ae-5d43db2b54b1.png)
「プロジェクトをアップグレード」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420158/1f191a4e-8141-bb15-ed65-e80f199d0ce8.png)
そうするとダイアログが出てくるの「購入」をクリックすればOKです。

## 再度、デプロイ

満を持して、再デプロイ！

```sh
$ npm run deploy

> functions@ deploy /Volumes/sd/tmp/dean/functions
> firebase deploy --only functions


=== Deploying to 'dean-015098'...

i  deploying functions
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
✔  functions: required API cloudfunctions.googleapis.com is enabled
✔  functions: required API cloudbuild.googleapis.com is enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (24.97 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: creating Node.js 12 function dean(asia-northeast1)...
✔  functions[dean(asia-northeast1)]: Successful create operation.
Function URL (dean): https://asia-northeast1-dean-015098.cloudfunctions.net/dean

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/dean-015098/overview
```

デプロイに成功しました！
https://asia-northeast1-dean-015098.cloudfunctions.net/dean にアクセスすると学部長を呼び出せます！！


## 実際に呼び出してみる
大学に呼び出されることはあっても、学部長を呼び出すことは稀...さぁ...どうなる！？
 
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓

```sh
$ curl https://asia-northeast1-dean-015098.cloudfunctions.net/dean
Error: could not handle the request
```

**しかし なにもおこらない**


## ログを見に行こう！
こういうときは先程のコンソールの「Functions>ログ」を見に行きましょう！
実行された関数のログがそこで見れます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420158/8088afab-58c3-d120-b6c1-577b291f73b5.png)

四角で囲ってあるところですね。

```sh
dean
Function execution took 60002 ms, finished with status: 'timeout'
```

「関数の実行に60秒以上かかったからタイムアウトだよ！」って内容ですね。

まぁ、当たり前ですね。無限ループしてるだけですから。

## タイムアウトを変更する。
もったいないので、タイムアウトを短くしましょう。

```js
- exports.dean = functions.region('asia-northeast1').https.onRequest((request, response) => {
+ exports.dean = functions.region('asia-northeast1').runWith({
+     timeoutSeconds: 1
+ }).https.onRequest((request, response) => { 
	response.status(200).send(dean());
});
```

これで１秒でkillされます。
なむなむ...


## 結論
　
　
　
　
　
　
　
　
　
　
　  
  
**学部長は寝てない！！**

　
　
　
　
　
　
　
　
　
おわり


## あとがき
Cloud Functions for Firebase、サクッと関数をデプロイできて便利ですね。
最近はFirebase Local Emulator SuiteにAuthのエミュレーターも追加されたりでFirebaseが熱いです。

話は変わりますが、最近、#TASK334という１４０文字で管理するTODOアプリを作っていたので、よかったら使ってください。
(習慣づくりとかに役立ったりと、意外と便利なのですが...なかなかユーザー数が伸びない...)

アプリ版(PWA) => [#TASK334](https://task334.app)
Chrome拡張機能=>[#TASK334 for Chrome](https://chrome.google.com/webstore/detail/task334/cflanppcpbjbjekfpmfhnodjbpebelpm?hl=ja)
紹介記事 => [1年半、Twitterでタスク管理をしてみて感じた7つのメリット]( https://note.com/notes/n596c737078d4/edit)

最後までお読みいただき、ありがとうございました。