Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8873 articles
Browse latest View live

Zoom Electron SDK 入門

$
0
0

Zoom Electron SDKでできること

 昨今の新型コロナ騒ぎの中でWeb会議サービスZoomが注目を集めています。Zoomは高機能で非常に便利なサービスですが、ちょっとカスタマイズして使いたいと思うこともあると思います。
 そんな時のために、エンジニアがZoomを使ったアプリケーションを簡単に作ることが出来るためのSDKが公式に配布されています。しっかりとしたデモが付属しているので、それを基本として必要な機能を拡張する形で開発を進めることが出来ます。
 この記事では、Zoom Electron SDKの導入から、いくつかの機能の使い方までをご紹介します。

Zoom App 登録(Tokenの取得)

 Zoom Electron SDKを始めとした開発者向けツールは、下のリンクから有効化できます。(Zoom App Marketplaceページの右上にある「Develop」メニュー内「Build App」からも同じページに移動できます)
https://marketplace.zoom.us/develop/create
 SDKを使うためには、Zoomアカウントでログインする必要があります。会社のアカウントなどを使うと、SDKのCreateボタンがグレーアウトされ、「Developer privilege is required. Please contact your account admin.」というメッセージが表示されることがありますので、その場合はGMailなどを使って適当に無料アカウントを作ってから作業を進めてください。
 SDKのCreateボタンを押すと、アプリケーション情報を入力する欄が出ます。ここは特にルールがある訳ではないので、適当に必要事項を入力してください。
App Name
App Information
 ここで必要事項の入力が終わると、次の画面(App Credentials)にSDK KeyとSDK Secretの二つの文字列が表示されます。同じページに戻ってくれば後からでも確認することは出来ますが、ここでテキストファイルなどにメモを取って保存しておくのをお勧めします。
 ここまでの詳しい手順は、ここにも説明されているので、そちらも参考にしてください。

SDKのダウンロード・初期設定

 登録はこれで終わりましたが、まだ肝心のSDK本体がありません。次は、必要なファイルをダウンロード・インストールしていきましょう。

下準備。

 まず、開発のためのプロジェクトフォルダを作りましょう。空フォルダで大丈夫なので、とりあえず作ってください。その後、ターミナルでそのフォルダに移動し、npm initコマンドを実行してください。package.jsonファイルが作られていれば準備完了です。

SDKのダウンロード

 Zoom Electron SDKは、こちらのGitHubで共有されています。CloneでもDownload Zipでも構わないので、とりあえず全部をローカルのプロジェクトフォルダ(前項で作成したフォルダ)に落としてください。特にDownload Zipを使った場合に、「(プロジェクトフォルダ)\zoom-sdk-electron-master\(いろんなファイル)」という形のフォルダ構成になっているかもしれませんが、それでは動かないので、GitHubからダウンロードした中身をプロジェクトフォルダに移すようにしてください。

Electronのインストール

 Zoom Electron SDKは、当然ながらElectronなしには動きません。npm install などを使ってElectronを入れるわけですが、ここで1つ罠があります。(npmコマンドが無いという方は、先にNode.jsをインストールしてください)
 Zoom Electron SDKはWindows 64bitに対応していないのです。なので、普通にnpm install --save-dev electronなどとするとWin64環境の方は後で失敗します。(具体的なエラーは後述)
 Macの方は普通にやれば問題ないのですが、Windowsの方はnpm install --arch=ia32 --save-dev electron@5.0.2 -gコマンドを代わりに使ってください。たぶん-gは付けなくても動くと思いますが、あとで出てくるコマンドがちょっと変わります。それから、Electronのバージョンは、SDKの更新に伴って変わる可能性があるので、「SDKのダウンロード」の項でご紹介したGitHubリポジトリのReadmeも合わせてご参照ください。(下の方に環境設定に説明があります)

NODEファイルのリビルド

 Zoom Electron SDKには、NODEファイルをリビルドするためのBATファイル(build_nodeaddon_win_ia32.bat)とSHファイル(build_nodeaddon_mac.sh)が入っています。自分の環境に合った方を実行してください。

デモの実行

 これで、Zoom Electron SDKを実行する準備が終わりました。試しにデモを実行してみましょう。run_demo_win.batとrun_demo_mac.shファイルがプロジェクトファイルにあるはずなので、自分のOSに合った方を実行してみてください。下の画面が出れば成功です。
demo.JPG
 このまま「Start Demo」ボタンを押して、画面の指示に従って必要な情報を入力すると、実際にZoomミーティングに接続することが出来ます。

デモ実行時のエラー

 Win64環境なのにElectronをインストールする際に --arch=ia32オプションを付け忘れた人は、File Not Foundのようなエラーが出ると思います。もしエラーが出たら、一回Electronをnpm uninstallコマンドで消してから、ちゃんと--arch=ia32オプションを付けて再インストールしてください。

デモファイルからアプリケーションへ

main.jsファイルの作成とパスの変更

 まずは、demo\main.jsファイルを、そのままプロジェクトフォルダにコピーしてください。これを使ってアプリケーションを作成します。
 これから、ファイル階層が変わったことをmain.jsに反映させていきます。main.jsの3行目、4行目のrequireメソッドの引数に渡されているパス '../lib/~' を './lib/~' に変更してください。同じように13行目のパスも変更したくなるかもしれませんが、このパスが使われるのはmain.jsではなくSDKのファイルなので、ここは変更しないで置いておきます。
 実は、もうこれだけでアプリケーションが走ります。ちゃんと設定できているか確認するために、実行してみてください。

最初に表示される画面を変更

 デモでは、機能を分かりやすくするために、Domainの指定とSDK Key/Secretの指定を手動で行いますが、普通のアプリケーションでは、そんなところは自動化されてユーザーログインから始まるはずです。最初に開かれるアプリケーション画面を変更してみましょう。

最初の処理を指定する方法

 アプリケーションを起動して最初に動くのは、2650行目のcreateWindow関数です。この中身を変更することで起動時の処理を設定することが出来ます。
 デフォルトでは、ShowDomainWindow関数の呼び出しが行われています。名前からも分かるように、これが最初の画面を開くための関数です。今はもう要らないので消してしまいましょう。

元の画面で行われていた処理を知る

 ただ、画面を開かないなら、最初のページで動いていた処理を代わりに自動で行ってあげないといけません。最初のページが行っていた処理を見るために、とりあえずHTMLファイルを読んでみます。pages\domain.htmlファイルを開いてください。
 ボタンが呼び出すのはdoinit関数で、その本体は入力の処理とsenddomainmsg関数の実行を行っています。そして、そのsenddomainmsg関数の本体はipcRendererのsend関数です。Electronでは、ipcRendererからsendされたメッセージはmain.jsのipcMainに渡されます(あんまり正確な表現ではないですが)。ここで、ようやくmain.jsの方に戻ってくることが出来ました。
 では、ipcMainが行っている処理を見てみましょう。その定義があるのはmain.jsの2639行目です。ここで、関数オブジェクトの配列functionObjのオブジェクトを呼び出しています。つまり、functionObjに適切なインデックスと引数を付けて呼び出せば、pages\domain.htmlが行っていた処理を自動化できるのです。
 ちなみに、functionObjの正体は、色々なモジュールに分かれているSDKの機能を一括してまとめ、渡す引数を自動的に整形してSDKに含まれる関数を実行するための関数群です。main.jsに書いてあるものですし、正確にはSDKの機能ではありませんが、分かりやすく便利なので、この記事では引き続きfunctionObjを使用して進めていきます。

処理を自動化

 最初に開かれるファイル、pages\domain.htmlで行われていた処理は、ipcRenderer.send('asynchronous-message', 'setDomain', domain, enable_log);でした。ipcMainに関する記述(main.js: 2639行目)を見ると、これはfunctionObj['setDomain'](domain,enable_log);として処理されています。つまり、この関数をmain.jsのcreateWindow内で呼び出せばいいということです。
 同様に、次に開かれるpages\index.htmlの処理も自動でやってしまいましょう。同じようにスクリプトを読むと、呼び出されているのはipcRenderer.send('asynchronous-message', 'sdkauth', sdkkey, sdksecret);で、これはfunctionObj['sdkauth'](sdkkey,sdksecret);として処理されています。
 この2つをcreateWindow関数で呼び出して解決、と行きたいところですが、実はfunctionObjの中に画面遷移に関する関数呼び出しが含まれているため、このままでは無駄にウインドウが出来ては消えてを繰り返すことになります。この部分も消して、挙動をより自然にしましょう。
 まずfunctionObj['setDomain']の方から見ていくと、630行目からの定義の中でProcSDKReady()という関数が呼ばれています。この関数定義は498行目にありますが、その中でshowAuthwindow()関数が呼ばれています。これが画面遷移の1つ目になります。もう必要ないのでコメントアウトするか消去してしまいましょう。
 続いて、functionObj['sdkauth']ですが、こちらは647行目からの定義を見ると、中でshowWaitingWindow()関数が呼ばれています。これも消してしまいましょう。

言語設定

 実は、functionObj['setDomain']の中でZoomで使用する言語を指定することが出来ます。635行目のlangid: ZoomSDK_LANGUAGE_ID.LANGUAGE_English,langid: ZoomSDK_LANGUAGE_ID.LANGUAGE_Japanese,にすると、Zoom使用言語を日本語にすることが可能です(デモページの言語は相変わらず英語のままですが、Zoomミーティングに接続した後のメニューなどが日本語になります)。ちなみに、その他の言語は lib\setting.js から見ることが出来ます。

その他

  • main.jsのfunctionObjは非常に優秀で、これだけでSDKの機能は大体使えます。基本的にはリファレンスを見ながら使いたい関数を探し、とりあえずfunctionObjに入っていないか関数名でファイル内検索をかけてみるのがお勧めです。もし入ってなかったとしても、functionObjの書き方を真似して書けば大体イケると思います。

  • コールバック関数を指定するメソッドを使うときは、なぜかリファレンスにもコールバック関数に渡される引数が書いていないので、libフォルダ内のjsファイルからデフォルトのコールバック関数の定義を見るのが良いと思います(それも丁寧な説明がある訳ではないので不親切ですが、ないよりはマシだと思います)。

  • Zoom開発に関する情報の場として、Zoom Developer Forumがあります。「electron」とかで検索をかけると情報が載っている場合もあるので、何か困ったら使ってみると良いと思います。

    おわりに

     Zoom Electron SDKは、日本語の情報も皆無ですし、リファレンスもあまり親切ではないですが、機能的には多くのモジュールと関数があり、応用の幅は非常に広いものだと思います。この記事をきっかけに使う方が増えれば幸いです。


Electron-builder実行時にpackage.jsonに記入したくない非公開設定を追加する

$
0
0

やれること

Electron-builderのCLIでの実行時引数に--config <追加の設定を書いたファイル.json>を渡すとpackage.jsonに記入していない設定を追加(上書き)できます。ただし、package.json内の"build"セクション以下の項目だけが対象です。

問題のある状態(隠すべき項目がpackage.jsonに書かれている)

electron--builderでappxをビルドする時、Microsoft storeで発行される"publisher"項目等を追加する必要があります。--configを使わない場合にはpackage.jsonに次のように書くでしょう。

{...(略)..."scripts":{"build-appx":"electron-builder -w appx"}"build":{"artifactName":"APPNAME_${version}_${os}.${ext}","appx":{"publisher":"CN=XXXXXXXXXXXXXXXXX","identityName":"YYYYYPUBLISHERNAME.APPNAME","applicationId":"PUBLISHERNAME.APPNAME","publisherDisplayName":"PUBLISHERNAME","languages":["JA-JP","EN-US"]},"win":{...(略)...}"mac":{...(略)...}}...(略)...}

この状態で

npm run build-appx

コマンドを実行すればappxのbuildが可能です。

ただし、これには問題があります。"publisher"の項目のCN=XXXXXXXXXXXXXXXXXの部分などは非公開にしたい情報だと思います。ですので、package.jsonに記入しておくのはよろしくないです。誤ってgithubなどに公開したないようにしなければなりません。

--configを使った対策

そこで、非公開にしたい"appx"に関する項目を外部ファイルに書いておいて、electron-builderの--config引数を利用して読み込むことにします。外部ファイルのフォーマットはymljsonが使えるらしいです。今回の例では外部ファイルをappx_setting.jsonという名前で作ります。

まず、package.jsonからは隠したい項目である"appx"に関する部分を取り除きます。さらに、"scripts:{"build-appx"}の中の引数として--config dist/appx_setting.jsonを追加します。こうしてpackage.jsonは以下の様になります。

json
{
...(略)...
"scripts": {
    "build-appx": "electron-builder -w appx --config dist/appx_setting.json"
}
"build":{
  "artifactName": "APPNAME_${version}_${os}.${ext}",
  "win": {
     ...(略)...     
  }
  "mac": {
     ...(略)...     
  }
}
...(略)...
}

隠し設定を書くappx_setting.jsondistディレクトリに作成し、中身は次のように記述します。packge.jsonから取り除いた部分をそのままコピペするだけです。

{"appx":{"publisher":"CN=XXXXXXXXXXXXXXXXX","identityName":"YYYYYPUBLISHERNAME.APPNAME","applicationId":"PUBLISHERNAME.APPNAME","publisherDisplayName":"PUBLISHERNAME","languages":["JA-JP","EN-US"]}}

これで先と同じ

npm run build-appx

コマンドを実行してコンパイルすると、あたかもappx_setting.jsonに記述された設定が、packege.jsonの"build"セクションに追加されたように振る舞います。

注意点

  • 忘れずにappx_setting.jsonは.gitignoreに記載して隠しておきましょう。
  • 上記の例でbuildを実行すると、生成されるappxのファイル名がpackage.jsonの"artifactName"で設定したものとは異なりデフォルトの名前になります。どうやら--configの振る舞いとして、"build"をまるまる入れ替えるという振る舞いをするようです。必要な項目はappx_setting.jsonの方に書いておきましょう。

さいごに

ElectronアプリをMicrosoft Storeに提出する記事を先日投稿しましたが、CN=XXXXXXXX等の非公開設定をどうするか、かなり悩みました。今回の方法が見つかって、よかったよかった。他にも色々使いみちがありそうです。

Puppteerでキャプチャ画像を取得 part1

$
0
0

概要

エンジニアもどき2年目に入りました。色々あって以下を行うLambda関数を作成することになりました。
色々学びがあったので備忘録がわり兼アウトプットのために書くことにしました。

  1. 特定のページのキャプチャ取得
  2. 取得したキャプチャをS3に保存
  3. S3に保存したURLをDyanmoに記録

part1では1. 特定のページのキャプチャ取得の実装とデプロイまで行います。
part1での実装内容はこちら

前提

以下の内容が出てきます。インストールなどの準備は完了している前提です。

  • nodejs
  • ServerlessFramework
  • yarn
  • webpack
  • iamのユーザ作成済み

実装作業

1. 準備

フォルダとテンプレートの作成をします。

mkdir puppeteer-capture
cd puppeteer-capture
serverless create --template aws-nodejs

作成されたテンプレートは次のような構成になります。
image.png

また、webpackを使用する(modeule構文などesnextで実装する)ため次のパッケージも追加します。

yarn add -D webpack webpack-cli
yarn add -D @babel/core @babel/preset-env babel-loader
yarn add -D serverless-webpack

webpackの設定を書きます。

jsconfig.json
{"compilerOptions":{"module":"esnext","baseUrl":".",},"exclude":["node_modules"]}
webpack.config.js
constpath=require('path');constslsw=require('serverless-webpack');module.exports={mode:slsw.lib.webpack.isLocal?'development':'production',entry:slsw.lib.entries,devtool:slsw.lib.webpack.isLocal?'cheap-module-eval-source-map':'source-map',output:{libraryTarget:'commonjs',filename:'[name].js',path:path.join(__dirname,'.webpack'),},target:'node',module:{rules:[{test:/\.js$/,enforce:'pre',exclude:/node_modules/,include:__dirname,use:[{loader:'babel-loader',},],},],},};

以上で事前準備は完了です。

2. キャプチャを取得する関数の実装

キャプチャを取得してローカルに保存する関数を作成します。

2-1. ライブラリの追加

今回はブラウザを操作するpuppeteer-coreとAWS上でchromium(ブラウザ)を動かすchrome-aws-lambdaを使用します。また、ローカルでの動作確認用にpuppeteerも追加しています。

# ライブラリの追加
yarn add puppeteer-core chrome-aws-lambda
yarn add -D puppeteer

事前準備で追加したwebpackの設定を除き、package.jsonに以下の3つのライブラリが追加されていれば完了です。

package.json
{"devDependencies":{//・・・ 省略 ・・・"puppeteer":"^3.0.2",//・・・ 省略 ・・・},"dependencies":{"chrome-aws-lambda":"^2.1.1","puppeteer-core":"^3.0.2"}}

2-2. 実装

handler.jsにキャプチャの取得/ローカルへの保存を行う関数を実装します。

handler.js
import{args,defaultViewport,executablePath,headless,puppeteer}from'chrome-aws-lambda';import{writeFileSync}from'fs';constgetCapture=async(url)=>{// ブラウザの起動letbrowser=null;try{browser=awaitpuppeteer.launch({args,defaultViewport,executablePath:awaitexecutablePath,headless,});// ページに移動constpage=awaitbrowser.newPage();awaitpage.goto(url);// キャプチャの取得(フルページ、jpegを指定)returnawaitpage.screenshot({fullPage:true,type:'jpeg'});}catch(error){console.log(error);returnnull;}finally{if(browser!==null){awaitbrowser.close();}}};exportconstcaptureFunction=asyncevent=>{consturl=event.url||'https://google.com/';// キャプチャ取得constjpgBuf=awaitgetCapture(url);if(!jpgBuf){return{statusCode:500,body:'キャプチャの取得に失敗しました.'};}// ファイルに書き出しwriteFileSync('/tmp/hoge.jpg',jpgBuf);return{statusCode:200,body:'キャプチャの取得に成功しました。'};};

2-3. 動作の確認

まずはserverless.ymlを以下のように編集します。
regionやprofileの項目は各自の環境に合わせて記述してください。

serverless.yml
service:puppeteer-captureprovider:name:awsruntime:nodejs12.xregion:ap-northeast-1profile:privatestage:dev# 使用するプラグインplugins:-serverless-webpack# 関数の設定functions:captureFunction:handler:handler.captureFunction

以下を実行して動作の確認を行います。

# ローカルでの動作確認
sls invoke local--function hello -c ./serverless.yml

一瞬ブラウザが立ち上がり/tmphoge.jpgという名前でキャプチャ画像が取得できていればOKです。
任意のページのキャプチャを取得する場合は、以下のように実行します。

# 任意のページのキャプチャを取得
sls invoke local--function captureFunction --data'{"url":"https://qiita.com/"}'-c ./serverless.yml

デプロイ

作成したLambda関数をAWS上にデプロイします。

1. 準備

chrome-aws-lambda込みでlambdaにデプロイすると容量が結構大きくなってしまうためパッケージはLambda Layerに入れておき、そこから使うことにします。

まずは、Layersに配置するものを格納しておくディレクトリを作成し、パッケージを追加します。
ドキュメントによると[任意の名前]/nodejs/node_modulesにパッケージを追加する必要があるようです。

# ディレクトリの作成mkdir layers/nodejs
cd layers/nodejs
# パッケージの追加
yarn init -y
yarn add chrome-aws-lambda puppeteer-core

2. Layersの設定

Layersに格納するパッケージを格納しているパスと関数からの参照を行うための設定をserverless.ymlに記述します。
コンソールからもLayersの設定はできますが、今回は設定に関しては全てコンソールを使わずに行ないます。
Layer設定のname項目によりlambdaだけでなくlayerもstageでの切り分けができます。

serverless.yml
service:puppeteer-captureprovider:# ・・・ 省略 ・・・# 追加1 : Layer名などの環境変数environment:STAGE:${self:provider.stage}PREFIX:${self:service}-${self:provider.stage}CAPTURE_LAYER:${self:provider.environment.PREFIX}-capture-layer# ・・・ 省略 ・・・# 関数の設定functions:captureFunction:handler:handler.captureFunction# 追加2 : Layerの参照layers:-{Ref:CaptureLayerLambdaLayer}# 追加3 : Layerの設定layers:CaptureLayer:path:layersname:${self:provider.environment.CAPTURE_LAYER}

3. デプロイと動作確認

以下のコマンドでデプロイすると画像のような結果が得られます。

# デプロイ
serverless deploy       

image.png

後はデプロイした関数の動作を確認するだけです。

# デプロイした関数の実行
sls invoke --function captureFunction -c ./serverless.yml   
sls invoke --function captureFunction --data'{"url":"https://qiita.com/"}'-c ./serverless.yml

以下のようにレスポンスが返ってきていれば完了です。

image.png

lambdaは実行後、使用されたすべてのリソース(に格納されているすべてのファイルを含む)が破棄されてしまうので、/tmpではなくS3などに保存する必要があります。そこで次回は2. 取得したキャプチャをS3に保存するように修正します。

おわりに

特に大変だったのは次の3点でした。

  • ちゃんとwebpackを使う
  • ServerlessFrameworkだけで完結
    • コンソールでLayerのzip追加しなくても何とかできないか
  • Layerをstageで切り分ける

AWS Serverless Application Modelも気になるのでいずれはそちらも使ってみたいなぁ。

参考

【Babel】の使い方(Javascript・node.js)

$
0
0

専用のディレクトリ作成

$ mkdir babeltest

カウントディレクトリを移動

$ cd babeltest 

package.jsonを作成

$ npm init -y

上記のコードは、対話環境で対話する事なく、プロジェクト名など適当につけてファイル「package.json」を作成してくれます。

必要なライブラリをインストール

$ npm install--save-dev babel-cli
$ npm install--save-dev babel-preset-es2015

Babel CLIとES 2015のプリセットの2つをプロジェクトにインストールします。
その際、package.jsonに設定を保存する様に、「--save-dev」オプションをつけておきます。

.babelrcを作成

プロジェクトフォルダに「.babelrc」というファイルを作成し、そこに下記の内容を書き込みます。

{"presets":["es2015"]}

コマンドでJSファイルを変換

$ babel test.js -o test.out.js

上記の例では、test.jsファイルを変換したコードをtest.out.jsファイルに書き込んでいます。

AWS AmplifyのチュートリアルでJavascript(React)の環境を構築する

$
0
0

いつも忘れないように、コンセプトから。

コンセプト

・お金かけてまでやりたくないのでほぼ無料でAWSを勉強する
 →ちょっとしたサービスを起動すると結構高額になりやすい。
・高いレベルのセキュリティ確保を目指す
 →アカウントを不正に使われるととんでもない額を請求されるので防ぐ

前回は「AWSサーバーレスのWebアプリケーションをもっと勉強する」でいろいろ考察してみました。実際に触って試していきたいと思います。

まずはクライアントサイドから

実際に触って確認していくのですが、クライアントサイドの実装を選択します。前回も書きましたが、Angular、React、Vue.jsから選択します。
https://note.com/erukiti/n/na654ad7bd9bb
↑のサイトが参考になりましたが、Angularって下火になっているんですね。Google=偉いみたいなイメージがあるので、有力候補になりそうな気がしますけど、今はReactが良さそうです。そもそもJavascriptの経験は無いのでどれでもいいのですが、長い物には巻かれろの精神?でReactを選択します。

じゃあReactにするとして、どうやって勉強するか、なんですが、Amplifyなるものを見つけました。あまり知らなかったのですが、モバイル用のサービスかと思ってましたがWeb系もいけるんですね。チュートリアルもあるようなので、これがちょうどいいのでは?と思いました。ちなみに、よくよく考えると今更Web系ってのもどうなのかな?と思います。実際にサービスを利用する時にブラウザー経由で何かするよりも、アプリを利用することのほうが増えたと思います。なので、本来ならモバイル用のアプリを勉強すべきか?とも思ってしまったのですが、ぶれてしまうのでやめます。

Amplifyのチュートリアルを開始(事前準備)

以下のURLにアクセスしてチュートリアルを進めましょう。基本的にチュートリアルが良くできているので、私のスクリーンショットは補足的に使って頂けるとといかと思います。
https://docs.amplify.aws/start

Reactを選択します。結構いろいろなチュートリアルがあるんですね。
1.png

ここはそのままスタートするだけです。
2.png

nodeを持っていないので落としてきます。インストールは全部デフォルトです。バージョンは最新のものを持ってきました。要件もクリアしているので問題無いですね。
3.png

バージョンを確認します。オッケー。
4.png

次にGitも持っていないので落としてきます。ここも同じように最新のものを使います。
5.png

Gitもバージョン確認します。オッケー。
6.png

AWSアカウントは持っているので省略します。
7.png

Amplifyのインストール(事前準備その2):Option 2: Follow the instructions

以下の2つを行っていきます。
10.png

npm installします。下の画面のようにいくつかワーニングが出てますが、スルーしました。ちょっと見てみましたが、よくわからなかったです。。。
8.png

amplifyの設定を行います。チュートリアルを読めばわかりますが、マネジメントコンソールで実行していくので注意しましょう。そこまで複雑なことはないですが。。。
9.png

Reactの新しいアプリケーションを作る:Create a new React App

Reactの設定を行っていきます。ここも指示通り実行していきます。
11.png

こんな感じの画面で終了します。
12.png

Reactの設定がうまくいきました。
13.png

このまま何か進めるのかと思って、言語設定を日本語に変えてみましたが、ただ起動確認するだけだったので、これ以上は何もしません。
14.png

バックエンドを初期化する:Initialize a new backend

続いてバックエンドの設定です。Apnlifyの初期化を行っていきます。
15.png

設定項目だけ入れると、後は勝手にやってくれます。
16.png

ライブラリをインストールする:Install Amplify libraries

ライブラリを追加します。
17.png

フロントエンドをセットアップする:Set up frontend

ここもいくつかワーニングが出てましがスルーしました。1つだけ、優先度は低めですが脆弱性の情報が出てたので、指示通りコマンド実行しましたがうまくいきませんでした。コード見て修正する感じだったので諦めます。
18.png

index.jsを開いて追記します。が、この記載は間違っています。後になって気づきましたが、プログラミング経験者ならこんな素人っぽいミスをする人はいないでしょう。。。まぁ素人なんです。
19.png

APIとデータベース接続:Create a GraphQL API and database

データベースに接続していきます。
20.png

ここも指示通りAPIを追加していきます。
21.png

APIのデプロイ:Deploying the API

APIのデプロイです。Reactの初期設定やAnplifyの初期化に比べるとこの辺はわかりやすいです。
22.png

APIキーが取得できて終わりになります。
23.png

ここは確認です。consoleコマンドでマネジメントコンソールが起動するので、そこで設定されているかが確認できます。設定項目を自分で作ったわけではないので、できているのを眺める程度です。
24.png

フロントエンドの設定:Connect frontend to API

フロントエンドの設定を行います。エディタを開いてコピペします。
25.png

ローカル環境で実行:Run locally

npm startを行って起動します。先ほど間違っていたところでコンパイルエラーが発生しています。エディタで開いて修正したら勝手にコンパイルされました。標準エディタでATOMを指定したので、ファイル保存が検知されてコンパイルされるんですかね。便利。
26.png

画面が起動して終了です。NameとDescriptionに値を入れると下に追加されていきます。
27.png

この後、「Add authentication」と「Deploy and host app」が残っていますが、今日はここまで。簡単に環境が作れると言えばそうですが、Amplifyを使いこなすにはもうちょっと勉強しないと厳しいですね。どのコマンドと設定で何が作られていくのかはもうちょっと理解する必要がありそうです。

【Node.js】Node.jsをインストールする方法

$
0
0

Node.jsインストール

Node.jsの公式サイトにアクセスする。

「推奨版」を押下する。

パッケージが開き、「続ける」を押下する。

「続ける」を押下する。

「同意する」を押下する。

「続ける」を押下する。

「インストール」を押下する。

パスワードを入力し、「ソフトウェアをインストール」を押下する。

「閉じる」を押下する。

Node.jsインストール確認

ターミナルを開き、下記コマンドを入力し、バージョンが表示されれば確認OK。

$ node -v
v12.16.3
$ npm -v
6.14.4

Node.jsで仮想通貨取引botを作るまで

$
0
0

この記事で得られること

  • Node.jsで仮想通貨取引bot(以下、bot)を作りたいときに参考にすると良いかもしれない資料
  • 筆者が作っためっちゃ基本的なbotのソースコード
  • 当たり前ですけどこの記事・ソースコードは実際の取引の参考にはしないでください。筆者はソースコードの内容でbotを短時間動かして普通に損失出しています(笑) 数百円ですが。
  • コード見ればわかりますが取引所はbitFlyerを使っています。

この記事の対象者

  • JavascriptおよびNode.jsの基本を終えて、何か作りたいなと思ってる人。
  • ここでいう基本は、Progateの関連するレッスンの内容は理解している、AtCoderのABCはJavascriptで解ける・ソースコード読めるレベルぐらいを想定しています。

参照した資料と進め方

Udemy: Node.jsの基礎から学ぶ、ビットコイン自動売買プログラム

  • この教材が作られたのが2年前(?)とかなので、ちょっと内容が古かったりします。ccxtというライブラリを使うんですが、ccxtのメソッドとかもけっこう変わっていたりするので、動画の内容と差があります。そこは自力で公式ドキュメントを参照するしかないです。
  • 売買アルゴリズムはどう書くかみたいな部分は参考になります。
  • Udemyに言えることですが、セールのときに買うのが良いです。1000円くらいで買えます。

ccxt(メインで使うライブラリ)

  • このページにメソッドの解説とかついてます。英語です。上の動画の内容と差がある場合はこちらを参照です。

JavaScript 非同期操作について学ぼう1(コールバック関数)

  • 上のライブラリを使いこなすにあたって、Javascriptの非同期処理を理解する必要があります。書き方がちょっととっつきにくかったりするので、最初にテキストで理解するよりこちらの動画のほうがわかりやすいです。

JS Primer

  • 非同期処理まわりについて、上の動画で理解できたらこちらのテキストで理解を深めるといいです。
  • このテキストは何かと参照するのでブクマ必須です。

ソースコード

2020/05/05 最初期のコードです。注文は出ますが、意図通りに注文が出ているかまで細かくチェックしていないというレベルです。ちゃんと動作確認して、もうちょっと無駄なところ消して、かっこいいロジック組んだら載せなおそうかな。

constccxt=require('ccxt');//基本情報・クラス作成constexchangeId='bitflyer',exchangeClass=ccxt[exchangeId],exchange=newexchangeClass({'apiKey':'APIキーをいれる','secret':'API Secretを入れる','timeout':30000,'enableRateLimit':true,});constinterval=3000;//apiにリクエスト飛ばす感覚constorderAmount=0.01;//注文量constrecords=[];//価格の格納letorderInfo=null;//注文情報の格納//待機時間設定constsleep=(timer)=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve();},timer);})};asyncfunctionmain(){while(true){//価格情報の更新constticker=awaitexchange.fetchTicker('FX_BTC_JPY');//今回はFXでやってます。records.push(ticker.ask);if(records.length>3){records.shift();}console.log("------");console.log(records);//注文がある場合の動作(利確・ロスカット動作)if(orderInfo){if(ticker.bid-orderInfo.price>50){//利確コースconstorder=exchange.createMarketOrder('FX_BTC_JPY','sell',orderAmount);orderInfo=null;console.log("sold: profit");awaitsleep(interval);continue;}elseif(ticker.bid-orderInfo.price>-50){//利確コースconstorder=exchange.createMarketOrder('FX_BTC_JPY','sell',orderAmount);orderInfo=null;console.log("sold: loss");awaitsleep(interval);continue;}console.log("hold");awaitsleep(interval);continue;//注文情報がある場合はそれ以上注文しない}//購入動作if(records[0]<records[1]&&records[1]<records[2]){console.log("buy: surge flag");constorder=exchange.createMarketOrder('FX_BTC_JPY','buy',orderAmount);//FXをせっかく使ってるのに買い一辺倒.成行注文です.orderInfo={order:order,price:ticker.ask,}console.log("bought");awaitsleep(interval);continue;}//ポジションもないし、注文しなかった場合console.log("skip - no position");awaitsleep(interval);}};main();

tsoaを使ってAPIドキュメントを自動生成

$
0
0

tsoa

tsoaとは、TypeScriptとNodeを使用してOpenAPI準拠のREST APIを生成するためのモジュール。
2020-05-05時点の最新バージョンはv3.0.8。

  • APIの唯一の信頼できる情報源としてのTypeScriptコントローラーとモデル
  • Express、Hapi、Koa対応
  • エンドポイントの説明などにはjsdocを使用

メリット

  • コメントと実装のずれがなくなる(JSDocで管理するモジュールもあるが、いつかずれる)
  • ドキュメント上からリクエストが投げれる

デメリット

  • デコレーター祭り
  • route.tsをプレコンパイルする必要があり、開発環境が少しだけ複雑に

導入方法

Introduction | tsoaを順に進めれば、以下のようなAPIドキュメントが http://localhost:3000/docsに表示されます。

スクリーンショット 2020-05-05 22.31.47.png

Controller部分をデコレーターを使用した書き方にする必要があり、
既存プロジェクトに導入するにはそこそこな作業が発生しそう。

JavaでOpenAPI準拠のREST APIを生成する場合は以下が参考になります。
https://qiita.com/ririkku/items/6e416d13567c6ee9e633

その他

tsoaのドキュメントのリポジトリ
https://github.com/tsoa-community/docs


Express Generator で作成されたファイルを触って Express を理解したい1:生成, yarn start, express.static, path.join

$
0
0

express-generatorによる生成、yarn startの意味、express.static()の意味、path.join()の意味についてまで。記述しています。

「余談」とある項目はやや踏みこんだ内容になっています。必要に応じて読み飛ばしてくだい。(むしろ余談のほうが多いね…)

生成

下記のコマンドで app_name以下に雛形を生成できる。

$ express app_name --view=pug

--view=pugを付けない場合、views以下のファイルの拡張子が .jadeになる。jade は pug に改名される予定(というか改名された)ので、今後は .pugに統一されていく(はず)。なので、これから expressのアプリを作るなら --view=pugを付けて生成したほうが良さそう。

生成のコマンド名は expressだけど、使用するには express-generatorをインストールする必要がある。

$ npm install express-generator -g

または

$ yarn add global express-generator

実行

$ yarn start

ポート番号を指定した場合は、PORT=8000 yarn startとする。ポートを 8000 とした場合、http://localhost:8000でアクセスできる。プログラムが正常に起動していれば、下記のページが表示される。

余談:yarn start と package.json の関係

コマンドラインから yarn startとすると、yarnpackage.jsonscriptsの中を見て、startに書かれているコマンドを実行する。ためしに package.jsonの中の starthogehogeにかきかて、yarn startしてみると…

package.json
..."scripts":{"hogehoge":"node ./bin/www"},...
$ yarn start
error Command "start" not found.

このように、エラーになる。そのかわり、yarn hogehogeで実行できる。package.jsonhogehogestartに戻すと、再び yarn startで実行できるようになる。

余談: yarn run

yarn runとすると、yarnscriptsの中を見て、どの項目を実行したいのかと聞いてくる。

$ yarn run
yarn run v1.22.4
info Commands available from binary scripts: acorn, babylon, cleancss, mime, uglifyjs
info Project commands
   - hogehoge
      node ./bin/www
question Which command would you like to run?: 

ここで startと入力すると、node bin/wwwが実行される。

express-generatorではデフォルトで実行コマンド名を startとしているので、yarn startとして実行しているにすぎずstartというコマンドが yarnにあるわけではない(らしい)。そのため、先のように自分でコマンド名を別のものに書き換えたり、追加することもできる。

たとえば、デバッグ時は 8000 番ポートにして、本番は 80 番にしたいみたいなときに、下記のように書いておく。

  "scripts": {
    "start": "node ./bin/www",
    "test": "PORT=8000 node ./bin/www"
  },

これで yarn testまたは yarn run testとすると、8000 番ポートで実行され、yarn startまたは yarn run startだと 80 番ポートで実行できるようになる。

addのような yarnに元からあるコマンドを "start"と同列に追加したい場合は、yarn addではダメで yarn run addとする必要がある(そんなことするのかは謎)。

余談: node bin/www と yarn start の違い

コマンドラインから node www/binとしてもアプリの実行はできる。この場合、package.jsonstartに書かれている内容は無視される。

前述のように、引数などを package.jsonstartに書いている場合は、node bin/wwwとしたときと yarn startで挙動が異なることになる。

生成ファイル

express generator 4.16.1--view=pugオプション付きで生成した場合、下記のファイル構造になる。

project_dir
├── app.js          # アプリのメインファイル
├── bin            
│   └── www            # yarn start 時に node bin/www として実行されるファイル
├── package.json       # ライブラリ等の依存関係やバージョン情報を格納したファイル
├── public          # static なファイルを置くフォルダ
│   ├── images         # http://localhost:8000/images 
│   ├── javascripts    # http://localhost:8000/javascripts 
│   └── stylesheets    # http://localhost:8000/stylesheets
│       └── style.css  # http://localhost:8000/stylesheets/style.css
├── routes          # router (ミドルウェア) 置き場
│   ├── index.js       # http://localhost:8000/ (トップページ)
│   └── users.js       # http://localhost:8000/users
└── views           # テンプレートファイル置き場
    ├── error.pug      # エラー時のテンプレート
    ├── index.pug      # index.js 用のテンプレート
    └── layout.pug     # index.pug や error.pug に読みこまれるテンプレート

static ファイル

下記の内容で index.htmlを作り、public直下に置く。

<!DOCTYPE html><head></head><body><h1>Hello World</h1></body></html>

この状態で PORT=8000 yarn startすると、http://localhost:8000で表示される内容は下記のようになる。

image.png

public以下にファイルを置くと、ブラクザから直接そのファイルにアクセスできるようになる。ただし、アクセスするパスは /publicではなく /になる。

たとえば、上記のように public/index.htmlを作成した場合、http://localhost:8000/public/index.htmlではなく、http://localhost:8000/index.htmlとしてアクセスできる。

http://localhost:8000のように、ファイル名を指定しない場合は、デフォルトで index.htmlが読みこまれる (ファイルが存在していれば)。

このような動作をするのは、app.jsに下記のコードがあるため。

app.use(express.static(path.join(__dirname,'public')));

この記述で、public以下のディレクトリにあるファイルは、http://localhost:8000/以下にあるものとして、そのままクライアントに返される。

単純な web サーバとして使うだけなら、public 以下にファイルを置くだけで十分に機能する。

余談:static ディレクトリの変更、追加

ディレクトリ名を public以外にすることもできるし、複数のディレクトリを指定することもできる。

app.use(express.static(path.join(__dirname,'public')));app.use(express.static(path.join(__dirname,'video')));app.use(express.static(path.join(__dirname,'img/hoge')));

この場合、いずれのディレクトリにあるフィルも、URL としては /にあるものとしてアクセスされる。dat/img/hoge.txthttp://localhost:8000/hoge.txtでアクセスできる。

static に指定されている複数のディレクトリに同名のファイルがある場合は、app.js内で、より先にある行の記述が優先される。たとえば、上のコードの状態で実行したとして、

public
├── images
└── index.html
    └── style.css

video
├── index.html
└── hoge.txt

img
└── hoge
    └── hoge.txt

このように、public/index.htmlvideo/index.htmlが存在している場合に、http://localhost:8000にアクセスした場合、public/index.htmlがブラウザに返される。同様にして、http://localhost:8000/hoge.txtへアクセスした場合は video/hoge.txtが返される。

余談:同じパスを示す app.use() は先にあるものが優先される

先に書いた行が優先というルールは、他の app.use()の記述にも適用される。

varindexRouter=require('./routes/index');app.use(express.static(path.join(__dirname,'public')));app.use('/',indexRouter);

app.use('/', indexRouter)という記述は、http://localhost:8000にアクセスがあった時に、./routes/index.jsの中を実行することを意味している。public/index.htmlが存在しなければ、app.use('/', indexRouter)が実行されて、下記のページが表示される。

しかし、public/index.htmlが存在していると、先にある staticの記述のほうが優先されて、public/index.htmlの内容が表示される。

app.use()の順序を逆にすると、優先順位も逆になる。

varindexRouter=require('./routes/index');app.use('/',indexRouter);app.use(express.static(path.join(__dirname,'public')));

この順序の場合、'public/index.html' が存在していても、app.use('/', indexRouter)の記述が優先される。

public/index.htmlを置くと、このような混乱が起こるので、分かっていてやるのでなければ避けたほうが良いと思う。もし分かってやってるなら、むしろ app.use('/', ... )の記述を消したほうが良い、また publicディレクトリ以下に、他の app.use()で使っているパス名と同じパスがあると、同様の混乱がおこりやすいため、それも避けたほうが良い(と思う)。

余談:path.join(__dirname, '') の意味

下記の index.htmlpublic/index.htmlに置いておき、

<!DOCTYPE html>
<head></head>
<body>
    <h1>Hello World</h1>
</body>
</html>

app.js内の、

app.use(express.static(path.join(__dirname,'public')));app.use('/',indexRouter);

この記述を、

app.use(express.static('public'));app.use('/',indexRouter);

このように変更し、アプリケーションのフォルダ内で yarn startしてみる。ブラウザで http://localhost:8000にアクセスすると、下記のページが表示される、はず。

しかし、bin ディレクトリに入ってから、下記のようにしてアプリケーションを実行し http://localhost:8000をブラウザで開くと…

$ cd bin
$ node www

このようになる、これは public/index.htmlが無視されて app.use('/', indexRouter);が実行されていることを意味する。

なぜこうなるかというと、node コマンドは実行したフォルダをカレントディレクトリとして認識するからのようだ。つまり、binディレクトリ内で node を実行すると、publicディレクトリを探す起点(ルート)が binディレクトリになってしまい、bin/publicを探しにいってしまうから、ということ。

binディレクトリ内に publicディレクトリが存在しない場合、app.use(express.static('public'));の記述は無視されて(マッチせずに) app.use('/', indexRouter);のほうが実行される。これは binが実行時のカレントディレクトリになり、express.static('public')という記述は、bin/publicディレクトリを指すようになるためである。

試しに、次のように書きかえると、binディレクトリ内で node wwwを実行しても、http://localhost:8000へアクセスしたときのブラウザには "Hello World" が表示されるようになる。

app.use(express.static('../public'));

ここで、下記のように path.join()を使った記述に戻すと、binディレクトリ内で node wwwとした場合でも "Hello World" と表示されるようになる。

app.use(express.static(path.join(__dirname,'public')));

これは、app.js (path.join()が記述されているファイル)があるディレクトリが __dirnameに自動的に入れられるため。つまり、path.join(__dirname, name)とすることで、__dirnamenameが連結され、結果的に app.jsのあるディレクトリを起点(ルート)として publicディレクトリを探してくれる。

アプリのフォルダ以外からアプリを起動することは、そうそう無いと思うのだけど、たまーにそういうことをしてハマることがある。特段の事情がない限り、実在するファイルやフォルダのパスを指定するときは path.join(__dirname, name)としたほうが良い

一方で、__dirname を app.useの第一引数や、外部参照するURLのパスの指定に使うとだいたいバグになる(分かっていたらやらないはず…)。

後述するように、routesディレクトリ内にあるファイルで path.join(__dirname, name)を使用したときは、routesディレクトリがカレントディレクトリ (__dirname) になる。app.jsのあるディレクトリではない。これはよく間違う。

また、app.use(path, ... )の一つ目の引数で指定する pathは、実在するファイルのパスを指定する引数ではない。この引数で path.join()を使うのは(分かってやっているのではない限り)誤りである。

余談:path でファイル名操作

pathjoin()以外にも、ファイル名を操作する便利な機能がいろいろある。詳しくは以下を参照のこと。

家庭内フードロスを減らそう。LINEbotを使ったレシピの提案

$
0
0

夏が近づき暖かくなってきました。
食品も傷みやすい季節になってきましたね!
また、在宅ワークも進み家でのお昼ご飯などのレシピにも困っているのではないでしょうか?

ということで、今回は家庭内でのフードロスが減るよう、余っている食材をメッセージするとレシピのリンクを返してくれるボットを作って見ました。

1 材料に関連したレシピがある場合:

2 材料に関連したレシピがない場合(ランダムで料理のカテゴリを提案します):

※ カルーセルやボタンテンプレートのサムネはprotoout studioさんの可愛いアイコンをかりました。

では説明していきます。

前提

基本的なLINEbotの作り方はここではあまり詳しく書かないので、初めての方は下記ページを参考にしてみてください。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

楽天レシピ系API

今回、レシピに関する情報は楽天レシピカテゴリーAPI楽天レシピカテゴリ別ランキングAPIを利用しました。
これらのAPIはドキュメントにあるように、基本的に料理のカテゴリーや、特定のカテゴリーでの上位4位までにランクインされているレシピを返してくれます。

準備

楽天レシピ系のAPIを利用するにはアプリIDの発行が必要なので、まず発行します。
image.png

発行が完了すると、APIを使うときに必要なアプリID/デベロッパーIDなどの情報が表示されるのでそれはメモしておきます。

ソースコード

早速ですが、ソースコードです。

recipe.js
'use strict';constexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;// 追加constaxios=require('axios');constconfig={channelSecret:'xxxxxx',channelAccessToken:'xxxxxx'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)app.post('/webhook',line.middleware(config),(req,res)=>{// LINE上で入力されたconsole.log(req.body.events[0].message.text);//ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。if(req.body.events[0].replyToken==='00000000000000000000000000000000'&&req.body.events[1].replyToken==='ffffffffffffffffffffffffffffffff'){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);//////////////// ここ以降が処理を書く場所 ///////////////asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''varresult=event.message.text;mes=result+'を使ったレシピ';//wikiのurlの前の一言awaitclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:mes});returngetMenu(event.source.userId,result);}//handleevent constgetMenu=async(userId,food)=>{constres=awaitaxios.get('https://app.rakuten.co.jp/services/api/Recipe/CategoryList/20170426?format=json&formatVersion=2&categoryType=small&applicationId='+{楽天のアプリID/デベロッパーID});varname=[];varcatgid=[];varurl=[];varlen=res.data.result.small.length;vara=0;for(vari=0;i<len;i++){if(res.data.result.small[i].categoryName.indexOf(food)>-1){name[a]=res.data.result.small[i].categoryName;catgid[a]=res.data.result.small[i].parentCategoryId;url[a]=res.data.result.small[i].categoryUrl;a=a+1;}}console.log(name);console.log(catgid);console.log(url);letcol=[];// 何も引っかからなかった時の処理if(name.length<=0){// とりあえず返事するawaitclient.pushMessage(userId,{type:'text',text:'ごめん、その食材の使い方は知らない。代わりのおすすめメニュー↓↓',});// ランダムでこの中の物を返すためのリストletcategory=[{categoryName:"人気メニュー",categoryId:"30",categoryUrl:"https://recipe.rakuten.co.jp/category/30/"},{categoryName:"定番の肉料理",categoryId:"31",categoryUrl:"https://recipe.rakuten.co.jp/category/31/"},{categoryName:"定番の魚料理",categoryId:"32",categoryUrl:"https://recipe.rakuten.co.jp/category/32/"},{categoryName:"卵料理",categoryId:"33",categoryUrl:"https://recipe.rakuten.co.jp/category/33/"},{categoryName:"パスタ",categoryId:"15",categoryUrl:"https://recipe.rakuten.co.jp/category/15/"},{categoryName:"鍋料理",categoryId:"23",categoryUrl:"https://recipe.rakuten.co.jp/category/23/"},{categoryName:"簡単料理・時短",categoryId:"36",categoryUrl:"https://recipe.rakuten.co.jp/category/36/"},{categoryName:"節約料理",categoryId:"37",categoryUrl:"https://recipe.rakuten.co.jp/category/37/"},{categoryName:"今日の献立",categoryId:"38",categoryUrl:"https://recipe.rakuten.co.jp/category/38/"},{categoryName:"健康料理",categoryId:"39",categoryUrl:"https://recipe.rakuten.co.jp/category/39/"},]varlen=category.length;varrand=Math.floor(Math.random()*(len+1));// 毎度ランダムに変わるようにするvarcategoryName=category[rand].categoryName+'のレシピだよ!';varurl=category[rand].categoryUrl;// ボタンテンプレートreturnclient.pushMessage(userId,{"type":"template","altText":"This is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"{サムネにしたい画像のURL}","imageAspectRatio":"rectangle","imageSize":"cover","imageBackgroundColor":"#FFFFFF","title":"おすすめMenu","text":categoryName,"defaultAction":{"type":"uri","label":"View detail","uri":url},"actions":[{"type":"uri","label":"レシピへGO","uri":url}]}});}else{varlen2=name.length;if(len2>10){len2=10;}// カルーセルの上限が10個なので10以上ある場合は10にする// "columns" の中身を作っておく  for(vark=0;k<len2;k++){consttitle=name[k];consttext=name[k]+'のレシピを紹介するよ!';consturi=url[k];letcarousel={"thumbnailImageUrl":"{サムネにしたい画像のURL}","imageBackgroundColor":"#FFFFFF","title":title,"text":text,"defaultAction":{"type":"uri","label":"View detail","uri":uri},"actions":[{"type":"uri","label":"レシピへGO","uri":uri}]};col.push(carousel);}returnclient.pushMessage(userId,{"type":"template","altText":"this is a carousel template","template":{"type":"carousel","columns":col,"imageAspectRatio":"rectangle","imageSize":"cover"}});}}(process.env.NOW_REGION)?module.exports=app:app.listen(PORT);console.log(`Server running at ${PORT}`);

説明

本当は材料をメッセージしたらその材料を使った料理を返すようにしたかったのですが、それは楽天レシピAPIをそのまま使うだけではできなかったので、まず使ったAPIは/Recipe/CategoryListのほうをcategoryType=smallで取ることで、入力した値をヒットしやすくなります。

入力値がAPIで返ってきた値とヒットした場合は、それを配列に入れてカルーセルで出すための準備をします。
もし、「ゴメス」のようなヒットしない値だった場合は、categoryType=largeの分類のボタンテンプレートでランダムに出すようにしています。

LINEのカルーセルやボタンテンプレートなどに関しては下記が参考になります。
■楽天のAPIを使ってマスクを買えるBotを作ってみた[LINEBotリッチメニュー]
■LINEの公式ドキュメント

作ってみて

厳密に食材に対してのレシピを返してくれるわけじゃないけど、まあまあ使えると思います。
ぜひお試しください!

Puppteerでキャプチャ画像を取得 part2

$
0
0

概要

エンジニアもどき2年目に入りました。色々あって以下を行うLambda関数を作成することになりました。
色々学びがあったので備忘録がわり兼アウトプットのために書くことにしました。

  1. 特定のページのキャプチャ取得
  2. 取得したキャプチャをS3に保存
  3. S3に保存したURLをDyanmoに記録

part2では2. 取得したキャプチャをS3に保存の実装とデプロイまで行います。
part2での実装内容はこちら

前提

前回(part1)の続きです。part1に引き続き以下の準備は完了している前提です。
前回(part1)の実装内容はこちら

  • nodejs
  • ServerlessFramework
  • yarn
  • webpack
  • iamのユーザ作成済み

実装作業

1. 準備

ローカルでの動作確認のためにローカルにS3のバケットを作成できるserverless-s3-localを使用します。

# 追加
yarn add -D serverless-s3-local

続いてserveress.ymlにS3の設定を追加します。

serverless.yml
service:puppeteer-captureprovider:# ・・・ 省略 ・・・# テーブル名やバケット名など環境変数environment:# ・・・ 省略 ・・・# 追加1 : バケット名CAPTURE_BUCKET:${self:provider.environment.PREFIX}-capture-bucketiamRoleStatements:# 追加2 : S3の設定-Effect:AllowAction:-'s3:PutObject'-'s3:PutObjectAcl'Resource:'arn:aws:s3:::${self:provider.environment.S3_BUCKET}*'# 追加3 : ローカルでの動作用設定custom:defaultStage:devs3:port:8081directory:.s3cors:false# 追加3 : プラグインplugins:-serverless-webpack-serverless-s3-local# ・・・ 省略 ・・・# 追加4 : リソースの設定resources:Resources:CaptureBucket:Type:AWS::S3::BucketProperties:BucketName:${self:provider.environment.CAPTURE_BUCKET}

ローカルで起動できるか確認します。

sls s3 start -c ./serverless.yml

以下のように起動していれば完了です。
image.png

ローカルにS3のバケットが作成されています。
image.png

また、AWSのリソース(S3やDynamoDB)を操作する必要があるので、aws-sdkを使用します。

# 追加
yarn add -D aws-sdk

Lambdaの実行環境には AWS SDK for JavaScriptが含まれているためdevDependenciesにのみ追加しています。
webpack.config.jsの外部依存にaws-sdkを追加して準備は完了です。

webpack.config.js
// ・・・ 省略 ・・・target:'node',// 追加externals:['chrome-aws-lambda','aws-sdk'],// ・・・ 省略 ・・・};

2. 実装

前回は取得したキャプチャ画像をローカルに書き出して保存していましたが、Lambdaは実行後、使用されたすべてのリソース(が破棄されてしまうので、S3に保存するように修正します。

handler.js
// ・・・ 省略 ・・・// 追加1 : S3のインポートimport{config,S3}from'aws-sdk';// 追加2 : S3の設定とオプション(LOCALで実行した際の設定)config.update({region:process.env.AWS_REGION});consts3Options=process.env.LOCAL?{s3ForcePathStyle:true,accessKeyId:'S3RVER',secretAccessKey:'S3RVER',endpoint:newAWS.Endpoint('http://localhost:8081'),}:{};// 追加3 : s3のクライアント/バケット名/アップロード関数consts3=newS3(options);constbucket=process.env.CAPTURE_BUCKET;constputObject=({key,body,contentType,acl})=>{constparams={Bucket:bucket,Key:key,Body:body,ContentType:contentType,ACL:acl,};console.log(params);returns3.putObject(params).promise();};constgetCapture=async(url)=>{// ・・・ 省略 ・・・};exportconstcaptureFunction=asyncevent=>{// ・・・ 省略 ・・・// 修正 : ファイルに書き出しからS3へアップロードするように変更// Before : ファイル書き出し// writeFileSync('/tmp/hoge.jpg', jpgBuf);// After : S3へアップロードtry{awaitputObject({key:'hoge.txt',body:jpgBuf,contentType:'image/jpeg',acl:'public-read',});}catch(error){console.log(error);return{statusCode:500,body:error.message};}return{statusCode:200,body:'キャプチャの取得に成功しました。'};};

注意する点はacl、つまりアクセスコントロールリストにpublic-readの設定を忘れるとS3にアップロードはできるもののAccess Deniedとなりリソースにアクセスができなくなってしまいます。

3. 動作の確認

まずはローカル環境での動作の確認です。

LOCAL=true sls invoke local--function captureFunction -c ./serverless.yml
LOCAL=true sls invoke local--function captureFunction --data'{"url":"https://qiita.com/"}'-c ./serverless.yml

実行して 200でレスポンスが帰ってくること、ローカルのバケットにキャプチャ画像が保存されていることを確認します。

image.png

バケットに保存されているhode.jpg._S3rver_objecthoge.jpgにリネームして開くと確認することができます。

image.png

4. デプロイと動作確認

デプロイします。

serverless deploy

実行し200でレスポンスが返ってくることを確認します。
また、AWSのコンソールからS3にアクセスしてバケットの中を確認します。

sls invoke --function captureFunction -c ./serverless.yml
sls invoke --function captureFunction --data'{"url":"https://qiita.com/"}'-c ./serverless.yml

image.png

以上でpart2の作業は完了です。
次はS3に保存した画像のURLをDynamoDBに書き出す処理までの実装を行います。

おわりに

  • ACLのところで結構引っかかった(Bucketに保存できるもののアクセスできない)
  • ローカルでのS3の使い方、ServerlessFrameworkでS3のバケットの作成など色々勉強になった
  • 今回は日本語のページのキャプチャは必要なかったので対応していませんが、Lambdaのインスタンスに日本語フォントが含まれておらず、日本語を含むページのキャプチャした際に文字化けが発生します。(フォントを用意して設定する必要がある)

参考

【Mac】VSCode 開発環境構築 (Java、Gradle、Node.js)

$
0
0

1. Java、Gradle、Nodeインストール

brewインストール

1)以下を実行してインストールする。

ターミナル
/usr/bin/ruby -e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Javaインストール

1)下記のサイトからJava SE Development Kit u** → Mac OS X → jdk-u**-macosx-x64.dmg をダウンロードし、インストーラを実行する。

Java8(Archive)
Java9(Archive)

2)環境変数の設定(バージョンに合わせて以下を実行)

ターミナル
export JAVA_HOME=`/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java_home -v"1.8"`source ~/.bash_profile

3)バージョン確認 

ターミナル
java -version#以下のように出力されればOK
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

Gradleインストール

1)以下を実行してインストールする。

ターミナル
brew update
brew install gradle

2)バージョン確認

ターミナル
gradle -v#以下のように出力されればOK------------------------------------------------------------
Gradle 6.3
------------------------------------------------------------

Build time:   2020-03-24 19:52:07 UTC
Revision:     bacd40b727b0130eeac8855ae3f9fd9a0b207c60

Kotlin:       1.3.70
Groovy:       2.5.10
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          1.8.0_144 (Oracle Corporation 25.144-b01)
OS:           Mac OS X 10.14.6 x86_64

Groovyインストール

1)以下を実行してインストールする。

ターミナル
brew install groovy

2)バージョン確認

ターミナル
groovy -v#以下のように出力されればOK
Groovy Version: 3.0.3 JVM: 1.8.0_144 Vendor: Oracle Corporation OS: Mac OS X

Nodeインストール

1)以下を実行してnodebrewをインストールする。

ターミナル
brew install nodebrew

2)バージョン確認

ターミナル
nodebrew -v#以下のように出力されればOK
nodebrew 1.0.1
:

3)以下を実行してnodebrewをインストールする。(v8.11.1を指定した場合の例)

ターミナル
mkdir-p ~/.nodebrew/src
nodebrew install-binary v8.11.1

4)使用バージョンの有効化

ターミナル
nodebrew use  v8.11.1

5)バージョン確認

ターミナル
nodebrew list
#以下のように出力されればOK
v8.11.1

current: v8.11.1

6)パスを通す

ターミナル
echo'export PATH=$PATH:~/.nodebrew/current/bin'>> ~/.bashrc
source ~/.bashrc

7)Nodeバージョン確認

ターミナル
node -v#以下のように出力されればOK
v8.11.1

8)npmバージョン確認

ターミナル
npm -v#以下のように出力されればOK
5.6.0

2. Git

1)以下を実行してインストールする。

ターミナル
brew install git

2)パスを通す。

ターミナル
echo'export PATH="/usr/local/bin:$PATH"'>> ~/.bash_profile
source ~/.bash_profile

3)バージョン確認

ターミナル
git --version#以下のように出力されればOK
git version 2.26.2

3. VSCodeインストール

本体はググってダウンロードする。

拡張機能のインストール

1)VSCodeを起動
2)コマンドパレットを開く(cmd+shift+p)
3)"Shell Command: Install 'code' command in PATH"を選択
4)ターミナルを開き、下記を実行

ターミナル
code --install-extension MS-CEINTL.vscode-language-pack-ja
code --install-extension VisualStudioExptTeam.vscodeintellicode
code --install-extension leizongmin.node-module-intellisense
code --install-extension vscjava.vscode-java-debug
code --install-extension vscjava.vscode-java-dependency
code --install-extension vscjava.vscode-java-pack
code --install-extension vscjava.vscode-java-test
code --install-extension vscjava.vscode-maven
code --install-extension vscjava.vscode-spring-boot-dashboard
code --install-extension vscode-icons-team.vscode-icons
code --install-extension shardulm94.trailing-spaces
code --install-extension ionutvmi.path-autocomplete
code --install-extension redhat.vscode-yaml
code --install-extension eamodio.gitlens
code --install-extension donjayamanne.githistory
code --install-extension CoenraadS.bracket-pair-colorizer-2
code --install-extension GitHub.vscode-pull-request-github
code --install-extension IBM.output-colorizer
code --install-extension emilast.LogFileHighlighter
code --install-extension stevencl.addDocComments

※詳細はMarketplace参照。下記URLのitemNameに--install-extension 以降を指定するとそれぞれ見ることができる。
https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-ja

インストールした拡張機能一覧を出力するコマンド
code --list-extensions | xargs -L 1 echo code --install-extension

5)以下、メニューから「Code > 基本設定 > 設定」で設定ファイルを開き、お好みでチェックして有効にする。(「設定の検索」にプロパティ名を入力するとフィルタリングされる。)
・insertFinalNewline
 ->ファイル保存時に最新の行を末尾に挿入
・editor.renderControlCharacters
 ->制御文字の表示
・terminal.integrated.copyOnSelection
 ->ターミナルで選択したテキストを自動コピーする
・editor.wordWrap
 ->折り返し表示。”on”にすることで折り返し有効。
・workbench.startupEditor
 ->起動時に無題ファイルを開く。"newUntitledFile"で無題ファイル、"none"で指定なしにも設定可能。

nodenvの環境構築

$
0
0

この記事では個人的にお勧めなnodenvのインストール方法と使い方について簡単に紹介していきたいと思います。

nodenvのダウンロード

まず、nodenvのソースをGitHubからcloneしてきます。

git clone https://github.com/nodenv/nodenv.git ~/.nodenv

nodenvのビルド

ダウンロードしてきたソースからnodenvをビルドして実行可能な状態にしましょう。

cd ~/.nodenv && src/configure && make -C src

pathを通す

コマンドを叩けるようにパスを通しましょう。

  • bashの場合
echo'export PATH="$HOME/.nodenv/bin:$PATH"'>> ~/.bash_profile
echo'eval "$(nodenv init -)"'>> ~/.bash_profile
  • Ubuntu DesktopやWindows Subsystem for Linuxの場合
echo'export PATH="$HOME/.nodenv/bin:$PATH"'>> ~/.bashrc
echo'eval "$(nodenv init -)"'>> ~/.bashrc
  • zshの場合
echo'export PATH="$HOME/.nodenv/bin:$PATH"'>> ~/.zshrc
echo'eval "$(nodenv init -)"'>> ~/.zshrc
  • Fish shellの場合
set-Ux fish_user_paths $HOME/.nodenv/bin $fish_user_pathsecho'eval (nodenv init - | source)'>> ~/.config/fish/config.fish

上記を参考に自分の環境に合ったコマンドを実行することでパスを通すことができます。

個人的にお勧めするパスの通し方はコンフィグファイルに以下のように記述する方法です。

bashzshで機能することを確認しています。

.zshrc
if[-e"$HOME/.nodenv"]then
    export NODENV_ROOT="$HOME/.nodenv"export PATH="$NODENV_ROOT/bin:$PATH"if command-v nodenv 1>/dev/null 2>&1
    then
        eval"$(nodenv init -)"fi
fi

このように記述してパスを通しておくことで、nodenvを入れる予定のないPCへ.zshrcファイルなどをコピーしたとしても、エラーを吐きません。

プラグインをインストールする

nodenvはnodeのバージョンを管理するためのコマンドですが、プラグインを入れることで、nodeのインストールも行うことができます。

まず、プラグイン用のディレクトリを作ります。

mkdir-p"$(nodenv root)"/plugins

次に、nodenv-buildをGitHubからcloneします。

git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build

これでnodenv-buildのインストールは完了です。

あとは、nodenvのアップデートなどをプラグインまで含めて自動で行ってくれるプラグインnodenv-updateを入れておくのをお勧めします。

git clone https://github.com/nodenv/nodenv-update.git "$(nodenv root)"/plugins/nodenv-update

これで、nodenv updateとコマンドを叩くことで、nodenvとそのプラグインを自動的にアップデートすることが可能です。

nodenvの使い方

ここまでの準備ができていれば、nodenvは以下のようにして使うことができます。

nodeのインストール

# インストールできるnodeのバージョンの確認
nodenv install--list# nodeのインストール(12.16.3は本記事執筆時点でのLTSバージョン)
nodenv install 12.16.3

# nodenvからnodeやグローバルなnpmパッケージを見えるようにするためにrehashを行う# 新しいnodeのバージョンを入れたり、npm install -gなどを行ったときに実行する必要がある
nodenv rehash

npm install -gのたびにrehashを行うのが面倒であれば
nodenv-package-rehash
のパッケージを入れることでそれを省略できます。

インストールしているnodeのバージョンの確認

nodenv versions

nodeのバージョンの指定

# グローバルで使うnodeのバージョンを指定する
nodenv global 12.16.3

# あるプロジェクトのディレクトリでのみ使うnodeのバージョンを指定する
nodenv local 12.16.3

nodenv localを実行することで、.node-versionという名前の不可視ファイルが生成され、その中にそのディレクトリで使うnodeのバージョンが記述されます。

gitなどで.node-versionを共有することで、プロジェクトのメンバーで使うnodeのバージョンを統一することもできます。

nodenvのアップデート

# nodenv-updateをインストールしているとき
nodenv update

# nodenv-updateをインストールしていないとき(cd"$(nodenv root)"; git pull)(cd"$(nodenv root)"/plugins/node-build &amp;&amp; git pull)

nodenvのアンインストール

~/.nodenvのディレクトリを消すだけです。

rm-rf`nodenv root`

まとめ

最後まで読んでいただきありがとうございました。
以上でnodenvのインストール・使い方は一通り終了です。
他にもいろいろな機能がありますので、公式のドキュメントを参考にして使っていってください。

参考

【Node.js】JTW生成処理をSAMで作る

$
0
0

前回の続きです。
Zoom会議をSlackをお知らせするアプリにてZoom APIに必要なJWTを生成する処理をSAMで作成しました。(下図の赤枠)
時間ができたらやりたいと思っていましたが、思ったよりすぐやっちゃいました。さすがStayHome週間(笑)
※本記事ではSAM,SAM-CLIなどの詳述は割愛します。ご了承ください。
スクリーンショット 2020-05-06 11.28.57.png

SAMを始める

公式にもありますが、まず↓のような前提があるので、それぞれやっておきます。

  1. AWSアカウントの作成
  2. IAMの設定
  3. Dockerのインストール(ローカル実行に必要)
  4. Homebrewのインストール(Linux,mac)
  5. SAM CLIのインストール

SAMテンプレートの取得

まずは、SAMテンプレートをDLしてきます。

sam init

これを実行します。今回ランタイムはNode.js 12.Xを選択しました。
テンプレートを取得すると以下のような感じのフォルダ構成で展開されるので、適宜編集しながら、処理を実装します。

sam-app/
   ├── README.md
   ├── events/
   │   └── event.json
   ├── hello_world/          
   │   └── app.js            #Contains your AWS Lambda handler logic.
   ├── template.yaml         #Contains the AWS SAM template defining your application's AWS resources.
   └── tests/
       └── unit/
           └── test-handler.js

JWT生成処理

↓のようなコードになります。

app.js
constjwt=require('jsonwebtoken')letresponse;constapiKey=process.env.API_KEY;constsecretKey=process.env.SECRET_KEY;/**
 *
 * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
 * @param {Object} event - API Gateway Lambda Proxy Input Format
 *
 * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html 
 * @param {Object} context
 *
 * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
 * @returns {Object} object - API Gateway Lambda Proxy Output Format
 * 
 */exports.lambdaHandler=async(event,context)=>{returnthis.generateJWT(apiKey,secretKey)};exports.generateJWT=function(apiKey,secretKey){try{// const ret = await axios(url);expire=Math.floor(Date.now())+(60000*15);consttoken=jwt.sign({iss:apiKey,exp:expire},secretKey,{algorithm:'HS256'});response={'statusCode':200,'jwt':token}}catch(err){console.log(err);response={'statusCode':500,'jwt':null}}returnresponse};

node-jsonwebtokenをいうAuth0のライブラリを用いてJWT生成処理を実装しています。

npm install jsonwebtoken

jwt.sign({iss: apiKey,exp: expire}, secretKey, {algorithm: 'HS256'});がJWT生成部分になるワケですが、issにZoomのAPIキー、expにはトークンがエクスパイアするまでの時間を入れます。エクスパイアする時間はエポックミリ秒で指定します。今回は、15分後を指定しています。

そして、生成したJWTを返すワケです。

SAMのデプロイ

ローカル実行

ローカルテストにあたり擬似的に環境変数を与えてやる必要があります。
ざっくりとした理解ですが、ローカルにLambda環境を模したコンテナを建てるので、そこではローカルホストの環境変数は使えないワケですね。

そのため、以下のような環境変数用の設定ファイルを用意しました。

env.json
{"GenerateJWT":{"ApiKey":"XXX","SecretKey":"XXX"}}

そして、template.yamlに↓を追加しましょう。

Resources:GenerateJWT:Type:AWS::Serverless::FunctionProperties:CodeUri:generate-jwt/Handler:app.lambdaHandlerRuntime:nodejs12.x#ここからEnvironment:Variables:API_KEY:!RefApiKeySECRET_KEY:!RefSecretKey#ここまで  

では、ローカルでテストしてみましょう!

まずは

sam build

ビルドして

sam local invoke --env-vars generate-jwt/env.json

generate-jwt/env.jsonを環境変数をして渡しています。
このやり方は以下のブログを参考にさせていただきました。

AWS SAM Local と LocalStack を使って ローカルでAWS Lambdaのコードを動かす

デプロイ

デプロイは簡単です。

sam deploy --guided

これだけ。あとはガイドに従いながら、質問に答えていくだけです。SAM-CLI素晴らしい。
ガイド内容はStack名は?とかリージョンは?とか。↓のように回答していきます。

Deploying with following values
===============================
Stack name                 : generateJWT
Region                     : ap-northeast-1
Confirm changeset          : True
Deployment s3 bucket       : 
Capabilities               : ["CAPABILITY_IAM"]
Parameter overrides        : {'ApiKey': '', 'SecretKey': ''}

このタイミングで今回のtemplate.yamlを載せておきます。

template.yaml
AWSTemplateFormatVersion:'2010-09-09'Transform:AWS::Serverless-2016-10-31Description:>GenerateJWT# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rstGlobals:Function:Timeout:3Resources:GenerateJWT:Type:AWS::Serverless::Function# More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunctionProperties:CodeUri:generate-jwt/Handler:app.lambdaHandlerRuntime:nodejs12.xEnvironment:Variables:API_KEY:!RefApiKeySECRET_KEY:!RefSecretKey#Events:#  GetJWT:#    Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api#    Properties:#      Path: /jwt#      Method: getParameters:ApiKey:Type:StringSecretKey:Type:StringOutputs:# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function# Find out more about other implicit resources you can reference within SAM# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api#GenerateJWTdApi:#Description: "API Gateway endpoint URL for Prod stage for GenerateJWT function"#Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/jwt/"GenerateJWT:Description:"GenerateJWTLambdaFunctionARN"Value:!GetAttGenerateJWT.ArnGenerateJWTIamRole:Description:"ImplicitIAMRolecreatedforGenerateJWTfunction"Value:!GetAttGenerateJWTRole.Arn

API Gatewayもデフォルトのテンプレートでは作成されるのですが、今回Step Functionsから呼び出すだけなこともあり、外しています。
JWTを返すAPIなんてデプロイしたくないし。

無事デプロイすると以下のようにターミナルに出ます。※更新の際のデプロイ結果です。

CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                      ResourceType                        LogicalResourceId                   ResourceStatusReason              
---------------------------------------------------------------------------------------------------------------------------------------------
UPDATE_IN_PROGRESS                  AWS::Lambda::Function               GenerateJWT                         -                                 
UPDATE_COMPLETE                     AWS::Lambda::Function               GenerateJWT                         -                                 
UPDATE_COMPLETE_CLEANUP_IN_PROGRE   AWS::CloudFormation::Stack          generateJWT                         -                                 
SS                                                                                                                                            
UPDATE_COMPLETE                     AWS::CloudFormation::Stack          generateJWT                         -                                 
---------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                      
----------------------------------------------------------------------------------------------------------------------------------------------
Key                 GenerateJWT                                                                                                              
Description         GenerateJWT Lambda Function ARN                                                                                          
Value                                             

Key                 GenerateJWTIamRole                                                                                                       
Description         Implicit IAM Role created for GenerateJWT function                                                                       
Value                                                     
----------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - generateJWT in ap-northeast-1

ところで、Parametersを定義しているApiKeySecretKeyの値がガイドの中で聞かれるのですが、ここでローカルの環境変数としてキー情報を渡してやりたかったのに、上手くできませんでした。。。環境変数はこの中では使えないのかな。。。?

Step Functionsで呼び出す

Step Functionsも今回のLambdaに合わせて下図のように変更しています。JWT処理に失敗すればLambdaが500のステータスコードを返すので、そしたらエラー通知用のSNSトピックにプッシュする感じです。

CloudWatch Eventでサイクル実行

日次で毎朝9時に実行したければ、以下のようにcronを定義します。

0 0 * * ? *

GMTなので、9時間戻し毎日0時を指定します。
今回は朝9時まで待ちきれず12:55に実行させました。お昼休み開ける前にZoom会議を作る!みたいなイメージですかね(笑)

結果は?

スクリーンショット 2020-05-06 14.38.47.png

無事成功しました。
ちなみに「密!」は自作です(笑)密を作らず、人と会うときはオンラインで。

[Node.js + PostgreSQL]Node.js アプリから PostgreSQL DB に接続

$
0
0

書くこと

Node.js で作成したアプリから、PostgreSQL DB に接続する方法

前提条件

接続するDBは、下記記事で作成したものを利用する。
[DB/SQL]要件をテーブルに落とし込む手法のメモ書き(複式簿記のテーブル設計を例に)

フォルダ構成

NodeJSSampleApp
┣ app.js
┣ node_modules
┣ package-lock.json
┣ package.json
┣ public
┗ views
  ┗ journal.ejs

コード

app.js
constexpress=require('express');constapp=express();app.use(express.static('public'));var{Client}=require('pg');varclient=newClient({user:'DB USER NAME',// DB のユーザー名を指定host:'localhost',database:'SampleApp',password:'DB PASSWORD',// DB のパスワードを指定post:5432})client.connect();varquery='select journal.id as 仕訳ID,journal.date as 日付, (select accounts_title.name from accounts_title where accounts_title.id = journal_details.debit_accounts_id) as 借方科目,journal_details.credit_amount as 借方金額,(select accounts_title.name from accounts_title where accounts_title.id = journal_details.credit_accounts_id) as 貸方科目,journal_details.credit_amount as 貸方金額,journal.memo as 摘要 from journal_details join journal on journal_details.journal_id = journal.id;';app.get('/',(req,res)=>{client.query(query,(error,result)=>{console.log(result);res.render('journal.ejs',{results:result});// results に格納した取得結果を journal.ejs で表示client.end();});});app.listen(3000);
views/journal.ejs
<html>
<head>
<title>複式簿記</title>
<link rel = "stylesheet" type="text/css" href = "/css/style.css">
<head>
<body>
<h1>複式簿記</h1>
<table>
<thead>
<tr>
<th>仕分ID</th>
<th>日付</th>
<th>借方科目</th>
<th>借方金額</th>
<th>貸方科目</th>
<th>貸方金額</th>
<th>摘要</th>
</tr>
</thead>
<% for(var i=0;i< results.rowCount;i++) {%>
<% var year = results.rows[i].日付.getFullYear(); %>
<% var month = results.rows[i].日付.getMonth() + 1; %>
<% var day = results.rows[i].日付.getDate(); %>
<tr>
<td><%= results.rows[i].仕訳id %></td>
<td><%= year + '/' + month + '/' + day %></td>
<td><%= results.rows[i].借方科目 %></td>
<td><%= results.rows[i].借方金額 %></td>
<td><%= results.rows[i].貸方科目 %></td>
<td><%= results.rows[i].貸方金額 %></td>
<td><%= results.rows[i].摘要 %></td>
</tr>
<% } %>
</table>

</body>
</html>

アプリを実行し、ブラウザから localhost:3000にアクセス

$ cd NodeJSSampleApp
$ node app.js

表示結果

2020-05-06 16.04.52.png


win7 + gulp + babel + browsify 速効構築

$
0
0

1. ディレクトリを作成

cmd.exe
mkdir gbb
cd gbb

2. パッケージを配置

package.json
{"name":"gbb","version":"1.0.0","description":"GulpBabelBrowsify","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1","gulp":"gulp"},"author":"","license":"ISC","dependencies":{"@babel/core":"^7.9.6","@babel/preset-env":"^7.9.6","babel-core":"^6.26.3","babel-preset-es2015":"^6.24.1","browser-sync":"^2.26.7","gulp":"^4.0.2","gulp-babel":"^8.0.0","gulp-concat":"^2.6.1","gulp-jshint":"^2.1.0","gulp-plumber":"^1.2.1","gulp-rename":"^2.0.0","gulp-sass":"^4.1.0","gulp-uglify":"^3.0.2","gulp-watch":"^5.0.1"}}

3. 構築

cmd.exe
npm start

4. 設定ファイル配置

gbb/gulpfile.js
vargulp=require("gulp");varsass=require("gulp-sass");varconcat=require('gulp-concat');varrename=require('gulp-rename');varuglify=require('gulp-uglify');varplumber=require('gulp-plumber');varbrowser=require("browser-sync");varwatch=require('gulp-watch');varbabel=require("gulp-babel");gulp.task("server",function(){browser({server:{baseDir:"./"}});});gulp.task("gulpjs",function(){gulp.src(_${元ファイルディレクトリ}_).pipe(plumber()).pipe(babel({presets:['@babel/preset-env']})).pipe(uglify()).pipe(rename({extname:'.min.js'}))// 5.pipe(gulp.dest(_${配置先ディレクトリ}_)).pipe(browser.reload({stream:true}));});gulp.task("watch",function(){gulp.watch(_${元ファイルディレクトリ}_, gulp.series('gulpjs'));});

5. 開始

cmd.exe
gulp watch

npx gulp watch//ローカルインストールの場合

5分でできるSwaggerの環境構築とAPI開発・テスト

$
0
0

今回のゴール

APIの標準ドキュメントとして、またモックサーバーとしてSwaggerはとても便利です。
おそらく今やRESTful APIを利用するほとんどの開発現場におけるデファクトスタンダードではないでしょうか?

今回はそんなSwaggerを利用して、以下をやってみます

  • Swaggerのローカルサーバーを立てる
  • APIを追加する(モックです)
  • APIをテストする

前提事項

npmやNodejsはインストールしておいてください。
今回は以下の環境で実施しました

node -v
v10.16.0

npm -v
6.9.0

Swagger環境のインストール

今回はNode.jsを利用して構築していきます

1. Swaggerモジュールをインストール

$ npm install -g swagger

2. Swaggerプロジェクトを構築

$ swagger project create hello-world

フレームワークを聞かれます。今回はNode.jsなのでexpressを選択

? Framework? (Use arrow keys)
  connect
❯ express
  hapi
  restify
  sails
$ cd cd hello-world

//依存性のあるモジュールをインストールします
$ npm install

サーバーを起動します

$ swagger project start

こんな感じでサーバーが立ち上がりました。ここまでで2分くらいでしょうか。
ここでhelloAPIを試してもいいです。私は5分でできると言っている手前、先に進みます🐱

スクリーンショット 2020-05-06 18.55.20.png

3. APIを開発する

APIが入っている/controllers配下に簡単なモックAPIを作りましょう。
実はこちら元々あるhello.jsをコピーして、helloをgoodbyeに変えた簡単なものです🐱

/controllers/goodbye.js
'use strict';/*
 'use strict' is not required but helpful for turning syntactical errors into true errors in the program flow
 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
*//*
 Modules make it possible to import JavaScript files into your application.  Modules are imported
 using 'require' statements that give you a reference to the module.

  It is a good idea to list the modules that your application depends on in the package.json in the project root
 */varutil=require('util');/*
 Once you 'require' a module you can reference the things that it exports.  These are defined in module.exports.

 For a controller in a127 (which this is) you should export the functions referenced in your Swagger document by name.

 Either:
  - The HTTP Verb of the corresponding operation (get, put, post, delete, etc)
  - Or the operationId associated with the operation in your Swagger document

  In the starter/skeleton project the 'get' operation on the '/hello' path has an operationId named 'hello'.  Here,
  we specify that in the exports of this module that 'hello' maps to the function named 'hello'
 */module.exports={goodbye:goodbye};/*
  Functions in a127 controllers used for operations should take two parameters:

  Param 1: a handle to the request object
  Param 2: a handle to the response object
 */functiongoodbye(req,res){// variables defined in the Swagger document can be referenced using req.swagger.params.{parameter_name}varname=req.swagger.params.name.value||'stranger';vargoodbye=util.format('Good bay, %s!',name);// this sends back a JSON response which is a single stringres.json(goodbye);}

続いてswaggerの定義ファイルであるswagger.yamlを更新しましょう。
追加する際はpaths:のhelloをコピーしてgoodbyeへ変更していきます。
これでGoodbyeAPIが画面に表示され、画面からテストすることができます。

/swagger/swagger.yaml
paths:/hello:# binds a127 app logic to a routex-swagger-router-controller:hello_worldget:description:Returns 'Hello' to the caller# used as the method name of the controlleroperationId:helloparameters:-name:namein:querydescription:The name of the person to whom to say hellorequired:falsetype:stringresponses:"200":description:Successschema:# a pointer to a definition$ref:"#/definitions/HelloWorldResponse"# responses may fall through to errorsdefault:description:Errorschema:$ref:"#/definitions/ErrorResponse"/goodbye:# binds a127 app logic to a routex-swagger-router-controller:goodbyeget:description:Returns 'Good Bye' to the API Caller.# used as the method name of the controlleroperationId:goodbyeparameters:-name:namein:querydescription:The name of the person to whom to say hellorequired:falsetype:stringresponses:"200":description:Successschema:# a pointer to a definition$ref:"#/definitions/HelloWorldResponse"# responses may fall through to errorsdefault:description:Errorschema:$ref:"#/definitions/ErrorResponse"

ここまでで4分24秒経過といったところでしょうか🐱

4. APIをテストする

Swaggerの表示しているブラウザをリフレッシュしてみましょう。
/goodbyeAPIが追加されたはずです✅

スクリーンショット 2020-05-06 19.02.46.png

早速Try this operationから実行しましょう。

Parametersに適当な文字列を入れて、Send Request🐱
レスポンスにSUCCESSが返ってきました😄😄
スクリーンショット 2020-05-06 19.03.55.png

いかがでしたでしょうか?
最も初歩的な使い方ですが、Swaggerの簡単さが伝わりましたら幸いです🐱

ソースコードも公開していますので、適宜ご利用ください
https://github.com/salthash329/swagger-sample

参考
https://github.com/swagger-api/swagger-node
https://github.com/swagger-api/swagger-node/blob/master/docs/quick-start.md

複数バージョンのNode.jsをPowershellスクリプトでインストール&切り替え

$
0
0

はじめに

Node.jsではモジュールをインストールすると小さなファイルがたくさんできるので、HDDだと動作が重く感じることがある。そこでNode.jsごとSSDにインストールし直したところ大分快適になった。

今回の再設定では、複数バージョンのNode.jsをPower Shellのスクリプトを使ってインストール(というよりダウンロード)した。というのも、複数バージョンのNode.jsのインストール・切り替えを行う便利なツールはあるものの、Windows非対応だったりMinGWが必要だったり、良く分からないExeファイルが必要だったりするため、職場の事情で使わせてもらえないかもしれないためだ。

Powershellスクリプトを使ったインストール

nodeswitchというスクリプトを作成した。中身はリンク先を見てもらうとわかるようにとても単純で、Invoke-Webrequestを使ってNode.jsのディストリビューションをダウンロードしたり、Add-Itemでシンボリックリンクを作っているだけなので、拡張するのは難しくないと思う。
スクリプト自体はPowershellだが、起動は以下のようにバッチファイルから行う。(Powershellはバッチファイルのように起動できないため)

  • install-node <Version>: 指定したバージョンのZipファイルをダウンロード・解凍する
  • switch-node <Version>: 指定したバージョンにパスが通るようにする

例えば、バージョン14.1.0をインストールしたければ install-node 14.1.0を実行する。これにより、64bit OSでは以下のようにversions以下にzipファイルが展開される。

[git clone で作られたフォルダ]
|
+- script
|  +- install-node.bat : インストール用のバッチファイル(中で common.ps1 をコール)
|  +- switch-node.bat  : バージョン切り替え用のバッチファイル(中で common.ps1 をコール)
|  +- common.ps1
|
+- common
|  +- node      : ディストリビューションごとのフォルダへのシンボリックリンク。PATH環境変数に追加すること。
|  +- npm_global: (同上) ここに、グローバルインストールしたモジュールが格納される。
|
+- versions
   +- node-v14.1.0-win-x64
   |  +- npm_global
   |
   +- node-v12.16.2-win-x64
      +- npm_global

npmの設定

Node.jsのパッケージマネージャ npm はデフォルト設定のままではうまく動かなかったり、AppDataにモジュールをインストールしたりしてしまうので、使う前にいくつか設定をしておく。

プロキシの設定

PCがプロキシサーバの内側にある場合は、npmでプロキシサーバの指定をする。職場では大抵この設定があると思うので、npmがうまく動かない場合は「インターネットのオプション」の「プロキシサーバ」の設定を確認し以下のコマンドでプロキシサーバを指定する。
image.pngimage.png

プロキシの設定
npm config set proxy http://your.proxy.server:8080
npm config set https-proxy http://your.proxy.server:8080

グローバルインストールの設定

npm はデフォルト設定のままではグローバルインストール先がAppDataフォルダになっている。これをSSDに変更すると、Angularのようにファイルが多いモジュールが快適に使えるようになる。この設定はインストール用のスクリプトinstall-node内で実施しているので、追加で実施する必要はない。

グローバルインストールの変更
npm config set prefix <グローバルインストール先>

キャッシュの設定

必須ではないが、npmのモジュールのキャッシュをSSDに置くと速くなるかもしれない。(初回は同じくらい)

キャッシュの設定
npm config set cache <キャッシュファイルを置く場所>

node-gypの設定

npm のモジュールの中には、sqlite3のようにnode-gypを使ったビルドが必要なものがある。node-gypの動作にはPythonとVisual Studio Build Toolsのインストールと、npm configの設定が必要である。Chocolateyであれば自動でやってくれるのだが、ここではnode-gyp - On Windowsの手順に従い、手動でインストール・設定する。実はそんなに難しくない。

Visual Studio Build Toolsのインストールと設定

node-gyp - On Windows「Option 2」のリンクから、Visual Studio Build Toolsのインストーラをダウンロードする。(この時はvs_buildtools__1947476439.1586275928.exeというファイルだった)

インストーラを開いたら「Visual C++ build tools」を選択し、インストール対象のコンポーネントにある「Visual Studio Build Tools 2019」をインストールする。インストール後に再度インストーラを起動すると、「インストール済み」と表示される。
image.png

Visual Studio Build Toolsをインストールしたら、以下のコマンドでnode-gypからどのビルドツールを使うかを指定する。(2019はインストールしたバージョンに読み替える)

VisualStudioBuildToolsの指定
npm config set msvs_version 2019

Pythonのインストールと設定

node-gyp はPython 2.7系と3.8系どちらにも対応しているが、ここでは3.8をインストールする。インストールしたら、以下のコマンドでnode-gypからどのPythonを使うかを指定する(パスは適宜読み替える)。

Pythonの指定
npm config set Python K:\python\python38\python.exe

ビルドできるか試してみる

これらの設定ができたら、ビルドが必要なモジュール(例えばsqlite3)をインストールできるようになる。

ビルドできることの確認
K:\test>npm install sqlite3
> sqlite3@4.1.1 install K:\test\node_modules\sqlite3
> node-pre-gyp install --fallback-to-build

node-pre-gyp WARN Using request for node-pre-gyp https download
node-pre-gyp WARN Tried to download(403): https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v83-win32-x64.tar.gz
node-pre-gyp WARN Pre-built binaries not found for sqlite3@4.1.1 and node@14.0.0 (node-v83 ABI, unknown) (falling back to source compile with node-gyp)
このソリューション内のプロジェクトを一度に 1 つずつビルドします。並行ビルドを有効にするには、"-m" スイッチを追加してください。
  unpack_sqlite_dep
  sqlite3.c
  win_delay_load_hook.cc
  ...以降ビルドの経過が表示される...
+ sqlite3@4.1.1
added 112 packages from 101 contributors and audited 162 packages in 45.258s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

おわりに

以上でNode.jsがとりあえず動く+ Sqlite3のようなネイティブのモジュールをインストールできるようになった。環境構築は地味だが、管理しないとバージョンアップで互換性がなくなったり、メンバ間のバージョン違いで不可解なトラブルが起きたりするので、インストーラに頼りすぎず、自前で環境構築用のスクリプトを作っておくのも良いかと思う。

⑧Rails + Docker環境から 脱Dockerをやってみた(2020.5時点)

$
0
0

経緯

当方、某テ●クキャンプ卒の未経験初学者です。
就活のポートフォリオのRailsアプリを制作する途中でDocker環境に移行したので、AWS/ECSでデプロイしようとしていましたが、アプリのレベル、自身の理解度、経費等を考慮しCI/CDの学習に方針を変更しました。

本稿は、Docker環境のRailsアプリをgit clone(ソースのコピー)をして、Dockerを外してRails sで開発する状態に戻す作業です。
初学者の迷走ゆえ、ベストプラクティスではないと思いますが、こんなこともあるのね、という感じで見ていただけたら幸いです。

前提

・Ruby on Rails 5.2.4.2
・Ruby 2.5.1
・MySQLl:5.6 の環境でアプリ開発していた

・ Docker version 19.03.8
・ docker-compose version 1.25.4 を途中から導入した
・ docker移行直前にgit cloneしていなかったため、git cloneでリポジトリ複製から始める

・ Githubリモートリポジトリがある
・ GithubDesktopで作業する

Rails + Docker環境のアプリ・ソースコードをコピーする( git clone )

Git hub リポジトリのページを開きます
Clone or download ボタンを押して URLをコピーします
image.png

コピーする先、場所(ディレクトリ)を決めます

今回は、当方のmacPCの

user/MyApp-deproyに、MyApp-deproyフォルダを新規作成しました

ターミナルでそこに移動します

ターミナル
cdMyApp-deproy
ターミナル
GitcloneURLペースト”+“スペース名前”でコマンドを実行しますクローン元の名前はnomadcafeです今回コピーして作成する名前は”nomadcafe-subにします実行するコマンドgitclonehttps://github.com/Sakagami-Keisuke/nomadcafe.gitnomadcafe-sub

こんな感じです nomadcafe-subと中身が出来上がりました
image.png

Githubリポジトリを作成する

GitHub desktop を開き、左上の Current Repository add をクリックし、Add Existing Repository をクリックします
image.png
Choose ボタンを押し、アプリを保存しているディレクトリを選択して Add Repository ボタンを押します
image.png

ローカルリポジトリは作成できましたが、クローン元と同じリポジトリ名になってしまいました
image.png

リポジトリ名(URL)を変更する

※参考にさせていただいた記事
Gitの設定をgit configで確認・変更

viエディッタで git config を編集します

ターミナル
gitconfig--edit実行

viエディッタが開きます
こんな感じです
image.png

viエディッタ
i」キーを押して、インサートモードにしますURLを変更したい名前にしますクローン元の名前 url=https://github.com/Sakagami-Keisuke/nomadcafe.git今回の名前に修正します url=https://github.com/Sakagami-Keisuke/nomadcafe-sub.git

こんな感じです
image.png

viエディッタ
esc」キーを押して編集を終了します:wq」キー+enterキーで上書き終了します

image.png
こんな感じです
image.png

先ほど Add Existing Repository で作成した、名前を修正したいリポジトリを右クリック Removeで削除します

削除できたら、もう一度、リポジトリを作成し直します
Current Repository add をクリックし Add Existing Repository をクリック
ディレクトリを選択します
image.png
image.png

git config --edit で記述したとおりのリポジトリ名で作成できました
image.png

リモートリポジトリを作成する

ローカル側(GitHub desktop)はできたので、リモートリポジトリを作ります

Githubリモート repositoriesページ でNewボタンをクリックします
image.png

・Repository name 欄: "nomadcafe-sub" を入力
・Publicを選択
・Create Repository ボタンをクリックします
image.png

リポジトリが作成できたのでクリックして入ります
image.png

これで、ローカルとリモートのリポジトリ名とURLが一致したので、紐づけができたと思われます
image.png

READMEをちょこっとだけ編集するなどして、差分を作ってから
Github desktop で publish branchボタンを押して Push Origin します
image.png

README のタイトル
“個人制作アプリ NomadCafe” を “個人制作アプリ NomadCafe-sub” に変更したものが反映されました
image.png
これでリポジトリの作成は完了しました

Dcoker環境をやめて、Rails単体に戻す

今回、以下を削除しました
Dockerを導入した時に追加したものです
・Dockerfile
・docker-compose.yml
・entrypoint.sh
・config/database.ymlの一部記述

image.png

こんな感じです
image.png

Rails s エラー:Could not find a JavaScript runtime.

Rails が起動するか確認します

ターミナル
railss

すると以下のエラーになりました

ターミナル
CouldnotfindaJavaScriptruntime.Seehttps://github.com/rails/execjsforalistofavailableruntimes.(ExecJS::RuntimeUnavailable)

image.png

参考にさせていただいた記事
rails sコマンド実行時に「Could not find a JavaScript runtime.」とエラーが出る場合の対処法

nodejsが無いため起こるエラーのようです
以下のコマンドを実行しますが失敗します

ターミナル
yuminstallnodejs--enablerepo=epelzsh: commandnotfound: yum
ターミナル
sudoyum-yupdatePassword:sudo: yum: commandnotfound

brew install node

解決に導いてくれた記事
【開発初心者】macでyumを使いたいのですが、上手くいきません。

抜粋
yumというパッケージ管理ソフトは、
Linuxのディストリビューション(OSの方言)の「Red Hat Linux」や「CentOS」で使われています。
Red Hat社が提供、その傘下で作成、サポートしている・
AWSのデフォルトイメージでも使われ、日本ではCentOSは強いLinuxディストリビューションのDebianやスピンオフ版のUbuntuでは、
yumではなくapt-getというパッケージ管理ソフトが使われている・
Macの世界ではHomebrewが強く、
CLIツールのパッケージは大抵Homebrewを使えば簡単に導入できる

以下のコマンドを実行します

抜粋
パッケージがあるかどうか調査brewinfonodenode: stable8.9.0(bottled),HEADPlatformbuiltonV8tobuildnetworkapplications発見したのでインストールbrewinstallnode

こんな感じです
nodeのインストールができたようです
image.png
image.png

Rails の起動を確認します

ターミナル
Railss

image.png
localhost:3000にアクセスします

ActionView::Template::Error (Asset application.css was not declared to be precompiled in production.Declare links to your assets in app/assets/config/manifest.js.

すると今度は下記のエラーでした

ターミナル
ActionView::Template::Error(Asset`application.css`wasnotdeclaredtobeprecompiledinproduction.Declarelinkstoyourassetsin`app/assets/config/manifest.js`.//=linkapplication.cssandrestartyourserver):7:%titleNomadcafe8:=csrf_meta_tags9:=csp_meta_tag10:=stylesheet_link_tag'application',media: 'all','data-turbolinks-track':'reload'11:%script{:src=>"https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"}12:=javascript_include_tag'application','data-turbolinks-track':'reload'13:app/views/layouts/application.html.haml:10:in`_app_views_layouts_application_html_haml___4171163319812110299_70145880673400

image.png

解決に導いてくれた記事
Sprockets::Rails::Helper::AssetNotPrecompiled in が出てコケた

config/initializers/assets.rb に下記の記述をします

ターミナルconfig/initializers/assets.rb
#省略Rails.application.config.assets.precompile+=%w( application.css )

image.png

次のコマンドを実行

rakeassets:precompile

Rails の起動を確認します

$railss

こんな感じです
image.png

localhost:3000にアクセス
無事、アプリが起動しました!
image.png

最後に

当方、
実務未経験+初学者+テッ○キャンプ卒業+転職活動中であります。
パッケージ、yum、brew、github、まだまだ経験不足でハマりました。
同一リポジトリでdockerを外す、環境を戻すことはケースとして起きにくいと思いましたが、なかなか事例が見当たらなかったので、ご参考までに投稿することにしました。

今回削除したDokerのセッティングは下記になります。
②Dockerを初めて導入してRails sする (2020.4時点)

今後も、学んだことをなるべくわかりやすく投稿したいと思います。
ありがとうございました。

Node.js とGoogle chartを使って新型コロナウィルス感染者数の推移をグラフ化した。

$
0
0

はじめに

組み込みエンジニアがGWにNode.js の勉強をした。
本を読んで基本的なことが理解できたので、何か実践的に役に立つことに使えないかと思い
全国の新型コロナウィルス感染者数の推移をグラフ化してみた。
以下がその結果である。ニュースでみるグラフと同じようなものが得られた。
image.png

やったこと

  1. 厚生労働省のホームページに掲載されている報道発表資料(https://www.mhlw.go.jp/stf/houdou/index.html) から、感染者数を取得する。

  2. 取得した感染者数と一日の増加人数をデータベース(mySQL)に収納する。

  3. Google Chart(https://developers.google.com/chart) を使って、感染者数の合計と増加人数をグラフ化する。

Node.js の環境設定

$ npm init
$ npm install ejs --save
$ npm install express --save

ホームページから取得したData を加工するために、cherrio-httpcli をインストール

$ npm install cherrio-httpcli --save

データベースのためにmysqlもインストール

$ npm install mysql --save

定期的に厚生労働省のDataを取得するために、node-cronも追加する。

$ npm install node-cron --save

cherrio-httpcliを使って、新型コロナウィルス感染者数を取得

まずは、感染者数を取得するために、厚生労働省のホームページで発表されている報道向け資料を参照することにする。
Dataの取得先を公的機関にして、信頼できる情報を取得することは重要だと思う。

報道資料のタイトルに以下のように複数のパータンがあるので、"新型コロナウイルスに関連した患者" と書かれているリンクを抜き出すようにした。

新型コロナウイルスに関連した患者の発生について(209~217例目)
新型コロナウイルスに関連した患者等の発生について(4月30日公表分)

/* 2-5月分の報道発表資料 */constmonthlyURL=["https://www.mhlw.go.jp/stf/houdou/houdou_list_202002.html","https://www.mhlw.go.jp/stf/houdou/houdou_list_202003.html","https://www.mhlw.go.jp/stf/houdou/houdou_list_202004.html","https://www.mhlw.go.jp/stf/houdou/houdou_list_202005.html"]constclient=require('cheerio-httpcli');varsearchClearlyResultsURL=function(url,results){constp=newPromise((resolve,reject)=>{client.fetch(url,{},function(err,$,res){$("li").each(function(idx){/* タイトルに"新型コロナウイルスに関連した患者"を含むURLを検索する */varisdata=$(this).text().indexOf('新型コロナウイルスに関連した患者');if(isdata!==-1){varanchor=$(this).find("a").eq(0);results.push({"title":$(this).text(),"href":anchor.attr("href"),});}});resolve(results);});});returnp;};getResultsURLs=(async()=>{varresultsURLs=[];for(letiinmonthlyURL){constresult=awaitsearchClearlyResultsURL(monthlyURL[i],resultsURLs);}}

次に、先ほど取得したリンクの中身である報道発表資料を解析して、感染者数と感染者数が発表された日付の部分だけを切り取る。
半角/全角/スペースの有無など、毎日書き方が微妙に違ったりするので、正規表現とか使って、無理やり切り出すようにした。

/* 全角を半角に変換する */functionZenkaku2hankaku(str){returnstr.replace(/[0-9]/g,function(s){returnString.fromCharCode(s.charCodeAt(0)-0xFEE0);});};/* 日付をYYYY/MM/DD のフォーマットにする */functiontoDate(str,delim){vararr=str.replace('','').split(delim)varm=("00"+arr[0]).slice(-2);vard=("00"+arr[1]).slice(-2);return'2020'+"/"+m+"/"+d;};functionapaptDate(str){returntoDate(Zenkaku2hankaku(str.replace('','/').replace('','')),'/');};varparseAvtiveNumber=function(url,results){constp=newPromise((resolve,reject)=>{client.fetch(url,{},function(err,$,res){vardate='';varactiveNum='';/* <div class="m-grid__col1"> のタグを抜き出す */$("div[class=m-grid__col1]").each(function(idx){/* 記事を一行ずつLoop を回す */varlines=$(this).text().split('\n');for(vari=0;i<lines.length;i++){/* 空行は無視する */if(lines[i]==''){continue;}/* 日付を取得 正規表現を駆使して抜き出す */varresult=lines[i].match(/[1-91-9]{1,2}[0-90-9]{1,2}\s?[)、].*/);if(result!=null){date=apaptDate(result[0].substr(0,result[0].indexOf('')+1));}/* 感染者数を取得 */varisdata=lines[i].indexOf('国内感染者は');if(isdata!==-1){varactiveText=lines[i].slice(isdata).replace(/[,,]/,'');varresult=activeText.match(/\d{2,6}/);activeNum=result[0];if(date!=''&&activeNum!=''){/* 日付と感染者数を配列に保存する */results.push({"date":date,"num":activeNum});break;}}}});resolve(results);});});returnp;};/* 先ほど取得した報道資料のURLでLoopを回す */for(letiinresultsURLs){varurl='https://www.mhlw.go.jp'+resultsURLs[i].href;constresult=awaitparseAvtiveNumber(url,resultsDatas);}

Node.jsとmysqlを接続する。

毎回、厚生労働省のページから情報を取得すると時間がかかるので、一度取得したものはデータベースに保存する。
差分があった時ののみデータベースを更新することにして、ページ更新時のアクセススピードを早くする。

今回は、mysqlをデータベースとして用意して、APIはaddDataとgetDataを用意した。
保存するDataは、"日付"と"合計の感染者数"と"一日あたりの増加人数"をデータベースに保存する。

varmysql=require('mysql');/* 自分の環境に合わせてData Baseは修正する必要あり */vardbConfig={host:'127.0.0.1',user:'root',password:'',database:'mhlw_stf'};varconnection=mysql.createConnection(dbConfig);exports.addData=function(data){varquery='INSERT INTO mhlw_data (created_date, Total_num, Numbyday) VALUES ("'+data.date+'", "'+data.num+'", "'+data.numByday+'")';connection.query(query,function(err,rows){});}exports.getData=function(){returnnewPromise(resolve=>{varquery='SELECT * FROM mhlw_data';connection.query(query,function(err,rows){console.log(rows);resolve(rows)});});}

Google Chartを使ってグラフ化

DBから取得してきたDataを以下のように、ejsを使ってGoogle ChartにDataを代入する。
今回は、一日の増加数と合計値を同時に表示したかったので、Combo Chartsを使うことにした。
一日の増加数を縦棒グラフ、合計値を折れ線グラフにする。

Google Chart にはこんな感じの配列を渡す必要がある。
[ '日付', '感染者数', '増加人数' ],
[ '2020/02/20', 93, 9 ],
[ '2020/02/21', 105, 12 ],
[ '2020/02/22', 132, 27 ],
[ '2020/02/23', 144, 12 ],
[ '2020/02/24', 156, 12 ],

app.js
varexpress=require('express');varejs=require("ejs");varapp=express();constdataManager=require('./datamanager.js');app.engine('ejs',ejs.renderFile);// Google Chart API へ引き渡すデータapp.get('/',(req,res,next)=>{console.log(dataManager.getResultsData());letdata={items:dataManager.getResultsData()};returnres.render("./charts.ejs",data);})varserver=app.listen(1234,function(){console.log('サーバを起動しました');});

ejs側でGoogle Chart用のjavascriptを読み込んでくる。

chart.ejs
<!DOCTYPE html><html><head><script type="text/javascript"src="https://www.gstatic.com/charts/loader.js"></script><script type="text/javascript">google.charts.load('current',{'packages':['corechart']});google.charts.setOnLoadCallback(drawChart);functiondrawChart(){vardata=google.visualization.arrayToDataTable(<%-JSON.stringify(items)%>);varoptions={title:'全国コロナ感染者数',hAxis:{title:'Date'},series:[{type:'line',targetAxisIndex:0},{type:'bars',targetAxisIndex:1}],};varchart=newgoogle.visualization.ComboChart(document.getElementById('chart_div'));chart.draw(data,options);}</script></head><body><divid="chart_div"style="width: 900px; height: 500px"></div></body></html>

最後に、以下のコマンドを実行して、

$ node app.js

ブラウザから、http://localhost:1234にアクセスすると以下のグラフが取得できた。

image.png

最後に

以上で、Node.js の全くの初心者でも、サーバー側の実装や、データベースの繋ぎこみ、グラフ化など簡単にできた。
今後は、厚生労働省の報道向け資料がupdate されるたびグラフも最新の情報が更新されることになる。

普段はCとかpythonを使っていて、javascriptは馴染みがなかったが、色々と勉強になった。
async/awaitだったり、funcitonの書き方に慣れない部分が多かったが、慣れてくると簡単に使えて便利。
これからも、もっと勉強してみようと思う。
今更ながら、javascriptはググると必要な情報がすぐに見つかるし、VSCodeでStep実行ができて、Debugもしやすいので、勉強しやすいと思う。

上で使ったコードは一部抜粋なので、完全なコードはこちらに置いておいた。
https://github.com/daiki0321/node-corona-active-count

参考にしたサイト

https://habataki-blog.com/output-chart-in-20minuites/

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

https://github.com/osamu38/node-express-curriculum/wiki/Node.js%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%82%92%E6%8E%A5%E7%B6%9A%E3%81%97%E3%82%88%E3%81%86

https://www.youtube.com/watch?v=pnsieVYy72M&list=PLwM1-TnN_NN7-zdRV8YsGUB82VVhfYiWW

Viewing all 8873 articles
Browse latest View live