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

Node.jsでCSVファイルを読みこんでJSONデータに変換するサンプルコード

$
0
0

Node.jsでShift_JIS で書かれたCSVファイルを取り扱う必要があり、その際の備忘メモ。
JSON変換までできた方が便利なので、csvtojsonを使ってみました。

要約

  • csvtojsonを使えば、標準的なCSVファイルを簡単にJSONデータに変更できました。
  • Node.jsはファイル読み込みは基本UTF-8を想定しているようですが、UTF-8以外の場合は iconv-liteをかましてShift_JIS→UTF-8変換してあげればOKでした。
  • csvtojson はコマンドラインからも呼び出せるので、コマンドラインでCSV→JSON変換できるのはなにげに便利です。

前提や環境

$sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.3
BuildVersion:   19D76

$node --versionv10.16.2
$

ちなみにサンプルデータは 住所.jpという日本の住所情報を用いてみました。

やってみる

$ git clone --branch 0.1.0 https://github.com/masatomix/csv-sample-node.git
$ cd csv-sample-node/
$ npm install

落としてきたプロジェクトのディレクトリ構成はこんな感じです。処理対象のcsvファイル(13tokyo.csv)だけは、上記サイトから落としておきましょう。

$tree
.
├── data
│   └── 13tokyo.csv  ←読み込むCSVファイル
├── src
│   └── index.ts
├── dist
│   ├── index.js
│   └── index.js.map
├── package.json
└── tsconfig.json

ソースコードはこんな感じ。

index.ts
importfsfrom'fs'importiconvfrom'iconv-lite'importcsvfrom'csvtojson'/**
 * 指定したパスのcsvファイルをロードして、JSONオブジェクトとしてparseする。
 * 全行読み込んだら完了する Promise を返す。
 * @param path
 */constparse=(path:string):Promise<any[]>=>{returnnewPromise((resolve,reject)=>{letdatas:any[]=[]fs.createReadStream(path).pipe(iconv.decodeStream('Shift_JIS')).pipe(iconv.encodeStream('utf-8')).pipe(csv().on('data',data=>datas.push(JSON.parse(data))))// 各行読んだらココが呼ばれるので配列にpush.on('end',()=>resolve(datas))// 全部終わったらココにくるので、resolveする})}if(!module.parent){// 呼んでみるparse('./data/13tokyo.csv').then((results:any[])=>{// 郵便番号が「100-000x」のものに絞ってみたresults=results.filter(address=>address['郵便番号'].startsWith('100-000'))console.table(results)// for (const address of results) {//   console.log(address)// }})}

実行してみます。

$npm run dev
>csv-sample-node@0.1.0-SNAPSHOT dev /Users/xxx/git/csv-sample-node
>ts-node src/index.ts
┌─────────┬─────────────┬────────┬─────────┬─────────────┬────────────┬────────┬───────┬───────┬──────────┬────────┬────────┬────────┬────────────┬──────────┬───────┬───────┬──────────┬────┬──────┬────────┬───────┬───────┐
│ (index) │    住所CD     │ 都道府県CD │ 市区町村CD  │    町域CD     │    郵便番号    │ 事業所フラグ │ 廃止フラグ │ 都道府県  │  都道府県カナ  │  市区町村  │ 市区町村カナ │   町域   │    町域カナ    │   町域補足   │ 京都通り名 │  字丁目  │  字丁目カナ   │ 補足 │ 事業所名 │ 事業所名カナ │ 事業所住所 │ 新住所CD │
├─────────┼─────────────┼────────┼─────────┼─────────────┼────────────┼────────┼───────┼───────┼──────────┼────────┼────────┼────────┼────────────┼──────────┼───────┼───────┼──────────┼────┼──────┼────────┼───────┼───────┤
│    0    │ '100000000' │  '13'  │ '13101' │ '131010000' │ '100-0000' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │   ''   │    ' '     │ '(該当なし)' │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
│    1    │ '100000400' │  '13'  │ '13101' │ '131010006' │ '100-0004' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '大手町'  │  'オオテマチ'   │    ''    │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
│    2    │ '100000200' │  '13'  │ '13101' │ '131010039' │ '100-0002' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '皇居外苑' │ 'コウキョガイエン' │    ''    │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
│    3    │ '100000100' │  '13'  │ '13101' │ '131010045' │ '100-0001' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '千代田'  │   'チヨダ'    │    ''    │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
│    4    │ '100000300' │  '13'  │ '13101' │ '131010051' │ '100-0003' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '一ツ橋'  │  'ヒトツバシ'   │    ''    │  ''   │ '1丁目' │ '01チョウメ' │ '' │  ''  │   ''   │  ''   │  ''   │
│    5    │ '100000500' │  '13'  │ '13101' │ '131010055' │ '100-0005' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '丸の内'  │  'マルノウチ'   │    ''    │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
│    6    │ '100000600' │  '13'  │ '13101' │ '131010057' │ '100-0006' │  '0'   │  '0'  │ '東京都' │ 'トウキョウト' │ '千代田区' │ 'チヨダク' │ '有楽町'  │ 'ユウラクチョウ'  │    ''    │  ''   │  ''   │    ''    │ '' │  ''  │   ''   │  ''   │  ''   │
└─────────┴─────────────┴────────┴─────────┴─────────────┴────────────┴────────┴───────┴───────┴──────────┴────────┴────────┴────────┴────────────┴──────────┴───────┴───────┴──────────┴────┴──────┴────────┴───────┴───────┘
$

ちゃんとShift_JISのCSVを読み込めています。
また、CSVの1行目のヘッダ行の文字列をJSONデータのプロパティ名として扱えてますね。簡単です。

おまけ: コマンドラインから使ってみる

npmに付属しているnpxコマンドを使うことで、csvtojsonをコマンドラインから呼び出せます。

$npx csvtojson ./data/13tokyo.csv 
{"�Z��CD":"101841500","�s���{��CD":"13","�s�撬��CD":"13101","����CD":"131010019","�X�֔ԍ�":"101-8415","���Ə��t���O":"1","�
$

あー Shift_JISはダメですね。ということでnkfをかましてみます。

$cat ./data/13tokyo.csv | nkf -S | npx csvtojson
{"住所CD":"101006101","都道府県CD":"13","市区町村CD":"13101","町域CD"
$

nkfコマンドは適宜Homebrewなどで入れておきましょう。
おつかれさまでした。

関連リンク・ソースコード


npm audit fixで解決しないとき

$
0
0

随分古いバージョンのパッケージを使っていたシステムで、node.jsをアップデートをしたときに

found 98 vulnerabilities (39 low, 13 moderate, 46 high)in 10622 scanned packages
  94 vulnerabilities require semver-major dependency updates.
  4 vulnerabilities require manual review. See the full report for details.

のような文言が出力されましたので、その対応を行います。

大体の場合は『npm audit fix --force』をすれば解決するようですが、今回は一部上手くいかないものがあったので、直接ファイルをイジイジして治すことにします。

(この辺はあまり自信がないので、もしかしたら間違えてるかもです)

どのパッケージが問題なのか調べる

まずは、問題の原因であるパッケージの確認を行います。

$ npm audit
=>┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Prototype Pollution                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ lodash                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in>=4.17.12                                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ kouto-swiss                                                  │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ kouto-swiss > prefiks > lodash                               │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1065                            │
└───────────────┴──────────────────────────────────────────────────────────────┘

こんな表示になるかと思います。

今回の私の場合だと、lodashなるパッケージのバージョンに問題があったようです。

どうやら Patched inの欄で指定されているバージョンにlodashを変更すれば解決するようです。

では、今入っているlodashのバージョンを確認してみます。

$ npm ls lodash
=> 
willjekylltemplate@1.0.0 /home/misato/Documents/Blogs/for-misato-fans
├─┬ browser-sync@1.9.2
│ ├─┬ dev-ip@0.1.7
│ │ └── lodash@4.17.12
│ ├─┬ easy-extender@2.3.4
│ │ └── lodash@4.17.15 
│ ├─┬ glob-watcher@0.0.7
│ │ └─┬ gaze@0.5.2
│ │   └─┬ globule@0.1.0
│ │     └── lodash@4.17.12
├─┬ grunt@0.4.5
│ ├─┬ findup-sync@0.1.3
│ │ └── lodash@4.17.12
│ ├─┬ grunt-legacy-util@0.2.0
│ │ └── lodash@4.17.12
│ └── lodash@4.17.12
└─┬ kouto-swiss@0.11.14
  └─┬ prefiks@0.3.3
    └── lodash@2.4.2 <=こいつが原因

npm auditでは、lodashの推奨バージョンは >=4.17.12になっていましたが、npm ls lodashで見てみると、ひとつだけ 2.4.2となっています。

普通はnpm audit fixで全てバージョンを調整してくれる?みたいなんですが、なぜかひとつだけバージョンが変更されなかったようです。

とりあえず、こいつを直します。

package-lock.json でバージョンを修正

package-lock.jsonで"lodash": "2.4.2"を検索すると、下記のような感じでヒットしました。

"requires":{"accord":"0.12.0","gulp-util":"3.0.8","lodash":"2.4.2","replace-ext":"0.0.1","stylus":"0.54.7","through2":"0.6.5"}

これを npm audit で調べた推奨バージョンに書き換える↓

"requires":{"accord":"0.12.0","gulp-util":"3.0.8","lodash":"4.17.12","replace-ext":"0.0.1","stylus":"0.54.7","through2":"0.6.5"}

後は、下記のコマンドを実行する。

$ sudo rm-r node_module
  => node_moduleの削除

$ npm install

私の環境では、これで解決しました!!

(と言いつつ、結局各パッケージのバージョンアップは諦めて、nodeのバージョンを下げたのですが・・・ソレはまた別の話ですな)

誰かの参考になれば幸いです。

Alexaスキルの開発 0から公開まで

$
0
0

初めて Alexaスキルの開発を始めてから公開するまでの流れについて。

フローチャートを作成する

事前に Alexaにどう発話したらどう分岐するかをまとめたフローチャートを作っておくとフローが整理できて開発が捗るので以下のような感じで作成します。(黒塗り多くてすみません)

alexa-flowchart.png

alexa developer console への登録

amazon alexaにアクセスし、アカウントを作成してログインします。
「スキルの作成」ボタンを押し、好きなスキル名を入力して作成。

スキルを作成した後、ビルドタブの左メニューから「エンドポイント」を選択し、表示される スキルIDを覚えておきましょう。

AWS Lambdaの利用

作成

AWSにログインしてLambdaにアクセスし、関数の作成ボタンを押します。
関数名を入力し、ランタイムは今回は「Node.js」を選択し、作成。
(自分が開発していた当時は東京リージョンだと必要な機能が揃っておらず、オレゴンリージョンを利用しました)

Alexa Skill Kit の追加

次にLambdaの作成した関数の画面にて、「トリガーを追加」で「Alexa Skill Kit」を選択し、スキルIDに先程 alexa deevloper console でコピーしたスキルIDを貼り付けて追加します。

ARNをメモ

画面右上の ARN値を覚えておきます。

再び alexa developer consoleに戻ってスキル設定

alexa developer console と Lambdaの紐付け

alexa developer console にて、スキルを選択したあとのビルドタブの左メニューから「エンドポイント」を選択し、
「Aws LambdaのARN」を選択して、「デフォルトの地域」に先程メモした ARN を貼り付けて「エンドポイントを保存」。

呼び出し名の設定

ビルドタブの「呼び出し名」をクリックし、なんという呼びかけでスキルが起動するかを設定します。

インテントの追加

ビルドタブのインテントの追加をクリックし、今回のスキルでAlexaが受け付ける可能性のある全発話をインテントとして作成していきます。例えば、

インテント名発話例
RecommendIntent「おすすめを教えて」「おすすめ見せて」「おすすめ開いて」
SelectNumberIntent「{number}で」「{number}でお願い」「{number}がいい」
SelectCategoryIntent「{category}にする」「{category}が見たい」「{category}を見せて」

といった感じ。

  • 各インテントにて、できる限り発話の揺らぎを網羅するようにします。足りていないとAmazonからのレビューで指摘されます。
  • 上記 {number}{category}のように、一部を変数化して、複数の発話を受け付けることも可能。変数のパターンは別途 スロットタイプに登録します。{number}など一部の変数は既に用意されています。
  • 肯定/否定の返事をするインテントや「前へ」「次へ」などのインテントは既にビルトインとして用意されているものがあるので、独自に追加せずにそれをビルトインインテントとして追加します。
  • CancelIntent (取り消し)、HelpIntent (ヘルプ)、StopIntent (中止)などいくつかのインテントはルール上、必ず実装しないといけない模様です。

Cloud9と連携する

LambdaとCloud9を連携させることで、Cloud9の高度なIDE上でAlexaスキル用の関数をコーディングすることができます。Cloud9を使わずにLambda内で書くことも可能ですが、Cloud9の方が使いやすくておすすめ。

環境の作成

AWSからCloud9を選択し、「Create Environment」を押して、名前などを適当に入力して環境を作成します。
Lambdaと同じリージョンで作成していれば、少し前に作成したLambda関数が右ペインの AWS ResourcesRemote Functionsに表示されているので、それをインポートすることでコーディングできます。
コーディングが終わったら Local Functionsを Deployすれば Lambdaに反映可能。

必要なパッケージのインストール

必要なパッケージをCloud9上のbashコンソールでインストールしておきます。

cd package.json が入ったディレクトリ
npm install ask-sdk --save

まずは、返事をするだけのスキルを作ってみる

Cloud9上に index.js を作成します。
どのディレクトリに置くかは、Cloud9でどの範囲をインポートしたかによるので一概に言えませんが、
もし package.json があればそれと同じ階層に作成します。

constAlexa=require('ask-sdk');/**
 * あいさつ
 */constGreetingHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;// 初回起動時returnrequest.type==='LaunchRequest';},asynchandle(handlerInput){returnhandlerInput.responseBuilder.speak('今日もおはようございます。').withShouldEndSession(true).getResponse();},};/**
 * 全てマッチしなかった場合
 */constFallbackHandler={canHandle(){returntrue;},handle(handlerInput){returnhandlerInput.responseBuilder.speak('申し訳ありません。もう一度お話しください。').reprompt('もう一度お話しください。').getResponse();},};constErrorHandler={canHandle(){returntrue;},handle(handlerInput,error){returnhandlerInput.responseBuilder.speak('エラーが発生しました。もう一度お話しください。').reprompt('もう一度お話しください。').getResponse();},};exports.handler=Alexa.SkillBuilders.standard().addRequestHandlers(GreetingHandler,FallbackHandler).addErrorHandlers(ErrorHandler).lambda();

とりあえずこんな感じ。GreetingHandlerが今回作ったメインのハンドラー。
開発は主に、各イベントや発話ごとのハンドラーを作成し、それをexports.handlerに登録していくことになります。
Handlerで必須なのは、canHandle()handle()で、前者はどのようなイベントや発話を受け取ったときにそのハンドラーを起動するかを設定し、handle()ではユーザーに画面や発話などを返したり裏で行う処理などをコーディングしていきます。

ハンドラーが複数あった場合、addRequestHandlers()内で指定した順に canHandle()での判定が行われ、最初にマッチしたハンドラーが起動します。
上記例の様に、どのハンドラーの条件にもマッチしなかった場合に必ず受け取るFallbackHandlerを作っておくと良さげ。(switch文の defaultみたいなイメージ)

デプロイ

コーディングが終わったら、Cloud9の右ペインの AWS Resourcesで作成した Local Functionsを選択して (Deploy)ボタンを押せば、Lambdaに反映されます。

作ったスキルのテスト

エミュレーター上でテスト

alexa developer consoleの「テスト」タブに移動し、入力欄に呼び出し名 (少し前の項目で設定した名前) を入力するとスキルが起動して、「今日もおはようございます」と返ってくることを確認できました。
今はまだ作り込んでいないので、これですぐにスキルが終了してしまいますが、この後の作り込みでもうちょっとやり取りができるスキルを作っていきます。

実端末でテスト

Alexaにアクセスし、アカウントを作って実端末と紐付けを行います。
ウィザードにしたがって進んでいくことで紐付けは完了します。

有効なスキル - 開発スキルに行くと、今開発中のスキルが表示されるので、ここでAlexaに開発中のスキルをインストールできます。(ここは記憶が定かでないので間違っていたらすみません)
あとは、実端末で「アレクサ、○○」と話しかければスキルが起動します。

ログを確認

スキルがうまく動作しなかった場合は、AWSのCloudWatchでログを確認できます。
CloudWatchのロググループをクリックすると作ったスキルが表示されるので、そこをクリックすることでログを確認できます。

スキルの公開

さて、まだ現時点では公開できる内容のスキルではありませんが、全体の流れを追うため、一旦スキルの作り込みは後回しにし、スキルの作り込みが終わった前提でスキルの公開までの流れを書いていきます。

alexa developer consoleの「公開タブ」にアクセスし、公開設定をおこなっていきます。
質問項目が多岐に渡りますが、最低限、必須項目だけでも頑張って埋めていきます。
全て入力が終わり、何か問題があれば指摘を受けますので修正します。
問題なければ「実行」ボタンを押すことで、自動テストが実行されます。
問題なければ、Amazonスタッフに検証依頼を出すことになります。
その後数日以内にフィードバックがありますので、修正等のやり取りを行い、最終的に問題なければ無事公開されます。

バージョン管理を行う

無事公開はできましたが、このままだとスキルを編集して反映すると即本番のスキルに影響が出てしまいます。
それだと問題があるので、Lambdaのエイリアス機能を使って、本番用と開発用を別々に管理していきます。

エイリアスの作成

Lambdaにアクセスし、「アクション」→「新しいバージョンを発行」で、今の最新版に対してバージョン名をつけます。名前は「1.0.0」など好きな名前をつけます。
次に「エイリアスの作成」で本番用のエイリアスを作成します。名前は「prod」など適当につけ、先程作ったバージョン番号を選択します。

スキルの向き先を変える

次に今作ったエイリアスのARNをコピーし、alexa developer consoleの「ビルド」タブの「エンドポイント」の「デフォルトの地域」の値を上書きします。
これで、今開発中のスキルは本番のエイリアスを向いていることになります。

再度、本番公開

この状態で、本番公開を進めれば、スキルは本番用のエイリアスを向いた状態でリリースされます。
その後またエンドポイントを$LATESTのARNに戻すことで、開発中のスキルは最新の状態を向きますが、本番スキルはエイリアスを向いたままとなり、プログラムを更新しても本番は影響を受けずにすみます。

その後の運用

スキルを更新する場合は、再度新しいバージョンを作ってエイリアスをそのバージョンに向け直せば、本番のスキルを更新できます。インテントの修正が無ければ再度Amazonに依頼を出す必要はありません。(インテントの修正がある場合は毎回依頼が必要)

少し面倒な点として、再度Amazonに依頼を出す際、開発中のスキルを本番用エイリアスに向けた状態で依頼を出す訳にもいきませんが、かといって何らかのエイリアスに向けておかないと本番公開されたときにエイリアスを向かなくなってしまい困ってしまいます。
したがって、もう一つ本番用のエイリアス (prod2) を作成し、そこに向けた状態で依頼を出します。
そうすることで、本番公開後、prod2に向いた状態でスキルが公開されます。
今後は、prodとprod2を交互に切り替えて依頼していくことになります。(もっと良い方法があれば…)

もうちょっと作り込みしてみる

問いかける

handle()の最後で以下のようなレスポンスを返すことで、ユーザーに問いかけを発信して、応答を待つことができます。

handle(handlerInput){// ~略~ 色々な処理// 問いかけreturnhandlerInput.responseBuilder.speak('あなたは男性ですか?女性ですか?').reprompt('あなたは男性ですか?女性ですか?').getResponse();}

なお、文章をAlexaが正しく読んでくれないときは、

'<phoneme alphabet="x-amazon-ja-jp" ph="オオダ\'シ">大田市</phoneme>'

という感じの文をspeak()に入れることで正しく読んでくれます。「\'」は日本語アクセントが低音に移る点を指定。

問いかけ後、返事を受け取る

canHandle()で受け取りたいインテントをキャッチすれば、そのハンドラーのhandle()が起動します。
以下の例は、肯定の応答を受けたときに起動する設定。

canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;// 「はい」と応答されたときif(request.type==='IntentRequest'&&request.intent.name==='AMAZON.YesIntent'){returntrue;}returnfalse;}handle(handlerInput){// 「はい」と応答されたときの処理}

会話の進捗によって処理を変える

例えば、「はい」「いいえ」で答える質問を2回に分けて投げかける場合、1つ前の方法だとどっちの質問か区別せずに反応してしまいます。そこで、現在の会話の進捗をセッション変数に保持しておき、その進捗に応じて処理を行います。

// 最初の質問constFirstQuestionHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;// 初回起動時returnrequest.type==='LaunchRequest';}handle(handlerInput){// セッション変数に現在の質問の位置を記憶constattributesManager=handlerInput.attributesManager;constattributes=attributesManager.getSessionAttributes();attributes.state='first';returnhandlerInput.responseBuilder.speak('あなたは男性ですか?').reprompt('あなたは男性ですか?').getResponse();}}// 2つ目の質問constSecondQuestionHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.requestif(request.type==='IntentRequest'){constattributesManager=handlerInput.attributesManager;constattributes=attributesManager.getSessionAttributes();// 1つ目の質問の後のときif(attributes.state&&attributes.state==='first'){// 「はい」か「いいえ」で答えたときif(['AMAZON.YesIntent','AMAZON.NoIntent'].includes(request.intent.name)){returntrue;}}}returnfalse;}handle(handlerInput){// セッション変数に現在の質問の位置を記憶constattributesManager=handlerInput.attributesManager;constattributes=attributesManager.getSessionAttributes();attributes.state='second';returnhandlerInput.responseBuilder.speak('あなたは20歳以上ですか?').reprompt('あなたは20歳以上ですか?').getResponse();}}// 結果constResultHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.requestif(request.type==='IntentRequest'){constattributesManager=handlerInput.attributesManager;constattributes=attributesManager.getSessionAttributes();// 2つ目の質問の後のときif(attributes.state&&attributes.state==='second'){// 「はい」か「いいえ」で答えたときif(['AMAZON.YesIntent','AMAZON.NoIntent'].includes(request.intent.name)){returntrue;}}}returnfalse;}handle(handlerInput){// 処理}}

問いかけ時に画面に情報を表示する

handle()のレスポンスでテンプレート機能を使うことで画面に情報を表示させることができます。
テンプレートはいくつか種類があるようです。
また、画面タッチにも対応させることができます。
ただし画面表示に対応していない端末を考慮して処理を分岐させるといいでしょう。

handle(handlerInput){// 画面表示に対応している場合if(handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display){constviewport=handlerInput.requestEnvelope.context.Viewport;// 画面が丸い端末の場合constisRound=viewport&&viewport.shape==='ROUND';handlerInput.responseBuilder.addRenderTemplateDirective({type:'BodyTemplate2',// 今回は背景画像とその上に文字を表示するテンプレートを選択token:'token',backButton:'HIDDEN',image:isRound?newAlexa.ImageHelper().addImageInstance('https://画像URL').getImage():null,backgroundImage:newAlexa.ImageHelper().addImageInstance('https://画像URL').getImage(),title:'タイトル',textContent:newAlexa.RichTextContentHelper().withPrimaryText('文章文章文章<br /><action token="detail">説明を聞く</action>').getTextContent(),});}}

上記で設置した「説明を聞く」ボタンのタッチは以下のようにcanHandle()内で検知することができます。

if(request.type==='Display.ElementSelected'&&request.token==='detail'){

スワイプして一覧の中から選べるようにする

スワイプして複数の商品から選択する、といったテンプレートもあります。

consttemplate={type:'ListTemplate2',token:'string',title:'選択してください。',backButton:'HIDDEN',listItems:[]};template.listItems.push({token:'detail_1',image:newAlexa.ImageHelper().addImageInstance('https://画像URL').getImage(),textContent:newAlexa.RichTextContentHelper().withPrimaryText('<font size="2">商品1</font>').getTextContent()});}template.listItems.push({token:'detail_2',image:newAlexa.ImageHelper().addImageInstance('https://画像URL').getImage(),textContent:newAlexa.RichTextContentHelper().withPrimaryText('<font size="2">商品2</font>').getTextContent()});}handlerInput.responseBuilder.addRenderTemplateDirective(template);returnhandlerInput.responseBuilder.speak('商品一覧を表示しています。画面を右へスワイプすると、すべての商品を見ることができます。商品の詳細を聞くには、画面にタッチするか、番号をおっしゃってください。').reprompt('商品を選択してください。').getResponse();

発話時に一緒にカードでメッセージを通知する。

returnhandlerInput.responseBuilder.speak('ありがとうございました。詳しくはお送りしたカードを参照してください。').withSimpleCard('購入した商品: XXX').withShouldEndSession(true)// スキルを終了する.getResponse();

スロットタイプの値を受け取る

前述のインテント作成の項目のような形でスロットタイプ型のインテントを作成した場合、スロットタイプのどの値が発話されたか、受け取ることができます。
例えば、{number}番目にすると発話された場合、numberの値を受け取ることができますので、その値に応じて処理を分岐できます。

// スロット値が渡された場合if((request.intent.slots.category.resolutions&&request.intent.slots.number.resolutions.resolutionsPerAuthority[0].values){constnumber=request.intent.slots.number.resolutions.resolutionsPerAuthority[0].values[0].value.id;}

次回起動時に前回の状態に応じた処理を行う

次回起動時に前回の続きから処理を行ったり、前回どう応答したかに応じて処理を変えたい場合は、DynamoDBを利用した、状態を永続的に記憶する機能を使います。

exports.handler=Alexa.SkillBuilders.standard().addRequestHandlers(// 略FallbackHandler).addErrorHandlers(ErrorHandler).withTableName('テーブル名').withAutoCreateTable(true).lambda();

こんな感じで機能を有効にしつつ、

asynchandle(handlerInput){constattributesManager=handlerInput.attributesManager;constpersistentAttrs=awaitattributesManager.getPersistentAttributes();persistentAttrs.hogehoge='変数に入れる値';attributesManager.setPersistentAttributes(persistentAttrs);awaitattributesManager.savePersistentAttributes();}

で状態を記録します。 awaitしないといけないので、メソッドに asyncをつける必要があります。

constpersistentAttrs=awaitattributesManager.getPersistentAttributes();if(typeofpersistentAttrs.hogehoge!=='undefined'&&hogehoge==='X'){returntrue;}

あとは、こんな感じで判定できます。前回終了直前に、今の状態を記憶しておいて、次回起動時、状態が記憶されていれば再開用のハンドラーを呼び出せばいいでしょう。
通常、起動優先度を上げるために、addRequestHandlersの上位のハンドラーで判定する必要があるでしょう。

APIと通信してその結果に応じた処理をする

普通に Axios などを利用すれば良いです。以下、一例。handle()に asyncをつけるのを忘れないようにする必要があります。

constaxios=require('axios');// 略constresponse=awaitaxios.post('https://APIのURL',parameters,{headers:{Authorization:'Bearer '+handlerInput.requestEnvelope.context.System.user.accessToken},validateStatus:function(status){returntrue;}});if(response.status!==200){

アカウントリンク機能を利用する

今回は省略し、必要に応じて別記事で書きたいと思います。

Amazon Payでの課金機能を利用する

今回は省略し、必要に応じて別記事で書きたいと思います。

Mac で nodebrew が動かない ( #node )

THE TYPESCRIPT WORKSHOPをやる時のハマりどころ・注意どころ

$
0
0

概要

THE TYPESCRIPT WORKSHOPをやった時に感じた、ハマりどころ・注意どころをメモしました。

Node.jsの相互互換性問題

JavaScriptでlambdaを書く部分を以下のように修正しないとSyntaxErrorがでます。
lambdaの設定で、runtime: lambda.Runtime.NODEJS_10_Xとしているので、Node.jsが`ES Moduleのsyntaxを理解できないことが原因です。

Before

hitcounter.js
import{DynamoDB,Lambda}from'aws-sdk';

After

hitcounter.js
const{DynamoDB,Lambda}=require('aws-sdk');

DynamoDBはcdk destroyで削除されない

気をつけましょう。
これはデフォルトの挙動で、RemovalPolicyRETAINになっているためです。
(確かにデフォルトの挙動で削除されたら恐いですし)

以下のようにすることでcdk destroyでDnynamoDBも削除できるようになります。

cdk-workshop-stack.ts
consttable=newdynamodb.Table(this,'Hits',{partitionKey:{name:'path',type:dynamodb.AttributeType.STRING},removalPolicy:cdk.RemovalPolicy.DESTROY});

Expressを習得するためにやったこと(随時更新)

$
0
0

Node.jsのフレームワーク、Expressを習得するためにやったことをここにまとめていきます。(随時更新!)

前提知識

Expressを使うためにはNode.jsのインストールが必要

※参考:初めてのNode.js:インストール確認、REPL、Hello worldまで

REST APIとは何か

※参考:REST APIとは何かを調べまくったらようやくイメージができてきたのでまとめた

Express事始め

まず、Expressとはなんなのか。JSフレームワークとの役割の違い。

※参考:https://www.i-ryo.com/entry/2020/02/13/083521

次はExpressでハロワ(Hello World)する予定。

随時更新していきます!

どこ住み?てかSlackやってる?生後10ヶ月のエンジニアが爆速10時間でChatbot制作!‬

$
0
0

[前置き]
閲覧ありがとうございます!エンジニア歴10ヶ月のひよっこwebでべろぱーです。
本投稿はエンジニア歴3年未満程度の初学者向け投稿になります。
個人で何か作りたいけど、丁度いい規模感のアイデアが思い付かない...
そんなあなたに!お手軽なChatbotの制作をオススメします!
というわけで作ってみましたー!↓↓

作ったもの -What I made-

🎉🎉地域の飲食店検索Slackチャットボット「GourmetNav」 🎉🎉

Slack上で動作するチャットボット。
「駅名」+ 「空白」 + 「店名に関わるキーワード」を入力すると、その地域の飲食店名とお店のURLを飛ばしてくれる(σ・ω・)σ
gourmetnav.gif

おもしろいところ

  • 地図を見ずに、駅名なんとなくの店名だけで検索できる。Slackで!!
  • チェーン店も、もちろん対象。目指せ、地域のスタバマスター!٩(。˃ ᵕ ˂ )وイェーィ♪

なにができるの

  • 「GourmetNav」の検索結果から、ワンクリックで各飲食店のサイトに飛べる。
  • わざわざブラウザで色んなページを見に行ったりしなくていい。
  • 検索結果にたまーに混じっている別の地域のお店、とかがない(←ここ重要!)

技術的な話

TL;DR (大まかなデータの流れ)

Slack× GAS× 駅情報取得API× ぐるなびAPIを使用しています。

  1. Slackのチャンネル「GourmetNav」から、「駅名」+ 「半角(または全角)空白」 + 「店名に関わるキーワード」を入力、送信。
  2. Slackから受け取った入力情報をGAS上で参照。
  3. 駅情報取得APIを用いて、入力情報内の「駅名」から緯度・経度を取得。
  4. ぐるなびAPIを用いて、上記の緯度・経度からその地域の飲食店を検索、一覧にしてGoogle Spreadsheetに出力(2度目以降はシートを初期化して更新するのでデータは保持しない)
  5. 生成されたSpreadsheet内の飲食店一覧から、「店名に関わるキーワード」と部分一致する「飲食店名」とその「サイトURL」を取得。
  6. 取得した「飲食店名」とその「サイトURL」をSlack側に出力。
  7. Slackのデータ容量圧迫しないように投稿を毎日自動削除するようにトリガーを設定(任意)

具体的な面倒くさいところ

1. まず、ローカルでの開発環境構築(任意)

ある程度のコード量/ロジックを書くならば、GASをブラウザ上のスクリプトエディタでベタ書きするのは、間違いなくNonsenseなんじゃないかと思います。(知らんけど)
(参考): Google Apps Script をローカル環境で快適に開発するためのテンプレートを作りました
(著者): @howdy39

※型定義ないとデバックしづらいのでTypeScriptも入れました(任意)

今回デバッグしていて初めて気付いたんですけど、Javascriptって配列にtypeofかけても「object」って返されるんですね。なんて暗黒言語!!
競技プログラミング(Atcoder)でC#愛好家の私には拒絶反応が出たので、typescript導入しました。
(参考): clasp + TypeScriptで課題改善botを作った
(著者): @mochisuna
(参考): JavaScriptの型などの判定いろいろ
(著者): @amamamaou

2. GASでSlackからの入力情報を受け取り、参照し、出力する(必須)

開発環境を整備したら、まずはSlackとBotによる簡単な入出力を実装する。
下記の記事を参考にすると、以下のようなことができる。
✔️GASでPOSTリクエストを受け取る
✔️SlackでOutgoing WebHookの設定を行う
✔️GASでSlackから渡された値を参照する
(参考): Slack上のメッセージをGoogleAppsScriptで受け取ってよしなに使う
(著者): @kyo_nanba

※このとき、SlackAppというライブラリを使用することで設定が少し楽になる

世の中のSlack愛好家の皆さんに圧倒的感謝!
SlackApp作成者@soundTrickerさんに三跪九叩頭しましょう。
(参考): Slack BotをGASでいい感じで書くためのライブラリを作った

3. 駅情報取得API, ぐるなびAPIを上記の中に組み込んで出力内容を工夫する

この部分に関しては、記事読んで実践するだけですんなり実装できました。
(参考): GoogleAppsScriptでぐるなびAPIから取得した駅周辺のお店をスプレッドシートに書き出す
(著者): @kouheidev

4. 上記参考記事を悪魔合体させながら論理構造を調整

今回は社内Slack上で作成したこともあり、一旦ソースコードは開示しません。
(まだまだ生後10ヶ月のエンジニア、トークンの扱いとかGASの仕様とか、セキュリティ面に関しては完璧に理解してるわけじゃないので!)
ただまあ、ベテランの皆さんなら多分苦もなく実装できると思います。(・ω・。)


苦労したところ

無限ループ🌀🌀

GAS上でのテストでは問題ないのに、
Slackで実際に入出力をテストすると無限ループが頻繁に発生しました。(恐ろしい...)
暫くして気付いたのですが、下記のロジックによるものでした。
ユーザの「A」という検索ワードに対して、「GourmetNav」は該当する検索結果を見つけてきます。
そして、「Aが見つかりました!」とSlackに出力してくれるわけです。
この出力を、GAS側は「ユーザによる新しい入力」として検知し、再び検索/出力処理を繰り返すわけです。
なるほど〜〜それはそうだわ! ということで、
doPost内の記述に下記を加えることで無限ループは解消します!

  if (e.parameter.user_name == "slackbot"){
    throw new Error("this is bot."); //入力者がbotだった場合、エラーを返します
  }

まとめ

Slackからのデータ入力をSpreadsheetに出力だとか、
Spreadsheet側の入力をSlackに出力するだとか、
こうした一方通行なBotならQiita上にも数多くあります

ただ、今回作成したのはInteractiveなChatBot。
ユーザ入力に対して、GAS側で入力を検知、
各API内を検索、Spleadsheetに検索結果を出力、
そして、ユーザの求めている情報にマッチする情報のみをユーザに提供する。

こうしたInteractiveなSlackチャットボット制作記事は、決して多くはないかと思います。
GASでSlack向けに開発している方にとって、何かの一助となれば幸いです。

ついったフォローしてね。(・ω・。)
@NadjaHarold

anyenv&nodenvの環境で、Node.jsのバージョンアップする

$
0
0

Node.jsのバージョンを複数保持し、コマンドひとつでバージョンを切り替えるanyenvを現場で使用しています。
厳密には、anyenvの中でnodenvというソフトウェアを動かすことで、Node.jsのバージョンを切り替えているのですが、今回初めての案件で新規作成を手掛けたので、忘れないようにメモとして残しておこうと思います。

ついでに、gulpを使用できるようになるところまで、解説します。

開発環境

・anyenvとnodenvでNode.jsのバージョン管理をしている。
gulpを使うためにNode.jsを使用。
・既にanyenvとnodenvは導入済み。

新規作成のおおまかな手順

・anyenvやnodenvを使用して、Node.jsのバージョンを最新にする
・npmのバージョンを最新にする
・gulpってコマンドを使用できるようにする
↓ 確認
・npm init -y
・npm install -D gulp(ここでnpm auit fixが出なかったら素敵)
・gulp -v

やってみる

実際にファイルを新規作成して作ってみます。

Node.jsのバージョンを最新にする

nodenvのバージョンは自動的にアップデートされないので、anyenvを使用して、アップデートが可能なファイルがあるかを確認します。
(node.jsに最新のバージョンがあったとしても、nodenvに反映されない)

以下のコマンドで、nodenvを最新のバージョンにします。

anyenv update

nodenvを最新にすることで、新しいバージョンのNode.jsも使用することができるようになっているかと思います。
以下のコマンドで、使用できるNode.jsのバージョンを確認してみましょう。

nodenv install —list

Node.jsの公式サイトをみたら、最新のバージョン・推奨バージョンを確認できます。

nodenv install {バージョン}

例)nodenv install 12.14.1

これで、anyenvとnodenvによる、Node.jsのバージョンアップが終わりました。
お疲れ様です!

このままでは、gulpのコマンドが使用できない(以下のようなエラーが出てしまう)ので、gulpのコマンドを使用できるように設定します。

nodenv: gulp: command not found
The `gulp' command exists in these Node versions:
  8.10.0
  9.2.0
  10.9.0

npm i -g gulp-cli

このコマンドを打ち込めば、gulpコマンドが使えるようになります◎

あとは、確認のために、package.jsonファイルの作成

npm init -y

gulpをインストールして

npm install -D gulp

gulpのバージョンを確認すれば終了です!

gulp -v

ここ以降は、自分で好きなgulpの設定をして、サクサク動かしていきましょう!

まとめ

Qiitaの記事を参考にしました!と連絡してくれた方がいて、とてもうれしかったです。この記事も誰かのお役に立てたら嬉しいです。

参考サイト

anyenvとnodenvの導入方法:https://www.to-r.net/media/anyenv/


雑に知ってしまったDockerを知り直す ~アプリケーション持ち込み編~

$
0
0

おさらい

第2回です。
前回の投稿はこちら
前回はnginxのイメージをpullして来てコンテナ起動。
ポートフォワードしてウェルカムページを表示させるまでやりました。

今回やりたいこと

  1. ローカルで適当にアプリケーションを用意する
  2. どうにかしてそのアプリケーションをコンテナの中にぶちこむ
  3. ホスト側から起動したアプリケーションへアクセスできるようにする

アプリケーションを用意

Nodeとgithubを勉強したときに作った、first-stepっていうオシャレ気取った名前のリポジトリが放置されていたので、これを再利用します。
せっかくなので触ったこと無いReactへ転生。

この辺のステップは本題と外れてしまうので割愛。
$ npm start
image.png

これをどうにかしてコンテナへ持っていきたい。

コンテナを用意

前回はnginxのイメージを利用しましたが、今回はnodeのイメージを利用します。
現在のnodeの最新バージョンはv13.8.0なんですが、ホストにインストールしてるのは安定版のv12.16.0なのでこれをインストールしたい。

バージョン指定してイメージをpullするのどうやるんだろう・・と思いつつとりあえずイメージを探します。
コマンドでも探せるみたいなんですが、初めてなのでGUIに頼ります。

https://hub.docker.com/
image.png

nodeのページを見つけた
image.png

下の方にズラーっとなんか並んでる
image.png

試しに検索窓にバージョン情報入れてみた
image.png

なるほどなるほど、なんかそれっぽいですね。

調べてみるとこれはTagと呼ばれるもので、古いのバージョンのイメージが欲しい場合はこのTagを指定してpullする必要があると。ただ、同じバージョン(v12.16.0)でもstretchとかslimとか更に違いがあるのが気になる。。
で更に調べると、例えばnodeの場合だとバージョンは同じでも無駄なファイルを省いて最適化されたslimバージョンがあったり、Debianのバージョンが異なるもの(stretchとかbusterとかはDebianのコードネーム)があったりと結構細かい違いがあるということが判明。
このあたりもちゃんと意識して適切なイメージを選択する必要があるってことか・・
で、更に気になったのがTag指定しない場合はどうなるんだ?とかlatest指定すると結局どれを引っ張ってくるのよとか、そもそもこれ誰が作ったイメージなん?安全なん?とか・・
TODO: これも色々調べてみたんですけど、今回の趣旨からは外れそうなのとボリューム膨らみそうなので別記事でまたやります。

今回はとりあえず名前がかっこいいのでbusterでやります。

$ docker pull node:12.16.0-buster
12.16.0-buster: Pulling from library/node
dc65f448a2e2: Pull complete
346ffb2b67d7: Pull complete
dea4ecac934f: Pull complete
8ac92ddf84b3: Pull complete
a3ca60abc08a: Pull complete
ab849ba5abe0: Pull complete
b9e0c215e971: Pull complete
766774022603: Pull complete
f199c0426b2a: Pull complete
Digest: sha256:66d5994de29952fe982729ef7c7f8d4c50d528279db386efbf373451f534fa16
Status: Downloaded newer image for node:12.16.0-buster

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node                12.16.0-buster      ce43ce61c1de        28 hours ago        882MB

$ docker run [イメージ名:TAG]
$ docker run -d --name node-react node:12.16.0-buster
505ba1fdefd2f705f90b083cee3653445256a0917e054147f8251d4e0eeef122

#前回はrunする前にイメージをpullしてなかったのでこのタイミングで色々ダウンロードしてましたが、
今回は事前にpull済なのでローカルのイメージを利用。カシコイ

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS               NAMES
8a126cad2450        node:12.16.0-buster   "docker-entrypoint.s…"   5 seconds ago       Exited (0) 4 seconds ago                       node-react

で、ここで作成したコンテナのSTATUSを見るとUp(起動中)ではなく、Exitedになっちゃってる。
前回のnginxの場合サーバを立ち上げてUp状態になっていたけど、今回のイメージはあくまでnodeの実行環境を用意するだけなのでコンテナが立ち上がりっぱなしにはならないのかな、となんとなく推測。
環境を構築して後続の命令が無いと、プロセスが死ぬようになっているらしい。起動するアプリケーションも無いのに立ち上げておく意味ないもんね。
-itdってオプションつけるとコンテナが立ち上がりっぱなしになるらしいのでお試し。

$ docker run -itd
-i ホストの入力をコンテナの標準出力につなげる。コンテナ側へ正しくコマンドを渡せるようにする。
-t コンテナの標準出力をホストの標準出力につなげる。ttyをコンテナに割り当てて、対話できるようにする。
-d コンテナに入らずバックグランドで起動する

$ docker run -itd --name node-react node:12.16.0-buster
10235f0996b7c1b1e5f47c5b1c36651ff4fe20cd72b14f47dc5a11f0e8607780

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
10235f0996b7        node:12.16.0-buster   "docker-entrypoint.s…"   3 seconds ago       Up 2 seconds                            node-react

うんうん、STATUSがUpになってる。
コンテナに入って作業したい場合は、とりあえず-itdって覚えても良いかもしれない。
TODO: 1個づつオプション外して試してみたけど、ここの挙動が上手く理解できていないので別の機会にちゃんと調べる。

# コンテナの中に入る
$ docker exec -it node-react /bin/bash
root@8bf7a6704bb9:/# node -v
v12.16.0

ちゃんと指定したバージョンで入ってますね

コンテナにアプリケーションをぶちこむ

最初は「ファイル転送でもすれば良いんかなぁ」とかかなり脳筋なこと考えていたんですけど、考えてみたらgithubからcloneしてきて起動すれば良いんじゃね?と思ったのでお試し。

root@8bf7a6704bb9:/# git --version
git version 2.20.1

git入ってる!勝った

# 適当なディレクトリへ移動して
root@8bf7a6704bb9:/# cd /usr/src/

# cloneして
root@8bf7a6704bb9:/usr/src# git clone https://github.com/wol-827/first-step.git
Cloning into 'first-step'...
remote: Enumerating objects: 22, done.
remote: Total 22 (delta 0), reused 0 (delta 0), pack-reused 22
Unpacking objects: 100% (22/22), done.

root@8bf7a6704bb9:/usr/src# cd first-step/

# アプリ起動に必要なモジュールインストールして
root@8bf7a6704bb9:/usr/src/first-step# npm i
found 0 vulnerabilities

# アプリケーション起動
root@8bf7a6704bb9:/usr/src/first-step# npm start
Compiled successfully!

You can now view sample-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://172.17.0.2:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.

起動できてそう!良い感じ

コンテナ起動時にポートフォワード設定し忘れてしまったので、改めて設定。
アプリケーションは3000番で起動していて、8080へ飛ばしたいのでこんな感じ。

$ docker run -itd -p 8080:3000  --name node-react node:12.16.0-buster
  • 起動前
    image.png

  • 起動後
    image.png

できた。ウレシイ。

まとめ

Dockerについて調べていると、環境を汚すことなくアプリケーションの実行環境を用意できる、なんでことが書かれているのをよく見るんですが身を持ってそれを実感しました。
ホスト側にDockerイメージだけ用意してあれば、nodeやらReact用のモジュールやらはコンテナの中にだけ入れるのでホスト側はキレイな体を保てると。専用のコンテナを立てるのでnodeのバージョンの切り替えも不要。
イメージはこれで、githubのリポジトリはこれ、コマンド実行手順はこれ、っていう風に準備すれば、とりあえず環境は手に入る。便利便利。

とはいえ今回の場合、コンテナの中に入ってcloneしたり色々コマンド実行しているのが冗長。
次はこの辺りをコマンド1発で出来るようにしたいと思います。たぶん出来る。出来ろ。

Node.js + Express + passport.jsでローカルサーバーにBasic認証ページを立てる

$
0
0

経緯

FlutterアプリのWebViewからBasic認証ページの接続テストをしたかったので、サンプルとしてローカルでBasic認証ページを作ることにした。
FlutterのWebViewからBasic認証ページにアクセスする - Qiita

ローカルサーバーを立てるだけならVSCodeのLive Serverでサクッとできたのだが、Basic認証のかけ方がいまいちわからなかったため、Node.js + Express構成を使うことにした。

環境はMacでnpmコマンドが使用できる前提。

Passport-HTTPのインストール

認証まわりが簡単に実装できるpassport.jsというライブラリを使ってみた。

Passport-HTTPをインストールする。

$ npm i express passport passport-http

view engine(テンプレートエンジン)にはejsを使うので、こちらもインストール。

$ npm install --save ejs

認証ページの準備

プロジェクトフォルダ直下にapp.jsファイルを作成。Basic認証をかけ、成功時にそれ用の表示を出すだけの簡素なつくり。

app.js
constexpress=require('express');constpassport=require('passport');constpassportHttp=require('passport-http');passport.use(newpassportHttp.BasicStrategy(function(username,password,done){if(username==='user'&&password=='pass'){returndone(null,true);}else{returndone(null,false);}}));constapp=express();app.set("view engine","ejs");app.get('/',passport.authenticate('basic',{session:false,}),(req,res)=>{res.render("index",{content:"Success"});});app.listen(3000,(err,res)=>{console.log('server is launched');});

viewsフォルダを作成し、その直下にindex.ejsを作成する。

/views/index.ejs
<!DOCTYPEhtml><html><head><metacharset="utf-8"/><title><%=content%></title>
</head>
<body><h1><%=content%></h1>
</body>
</html>

今回の目的はWebViewからBasic認証ページの接続確認をしたいだけなので、認証に使うusernameは「user 」、passwordは「pass」というガバガバセキュリティで行く。

ブラウザで表示確認

ローカルサーバーを起動。

$ node app.js
server is launched

ブラウザでlocalhost:3000にアクセスすると、ユーザー名&パスワードを聞かれる。
http://localhost:3000/

スクリーンショット 2020-02-16 10.40.05.png
正しいユーザー名&パスワードを入力して「OK」を押すと
スクリーンショット 2020-02-16 10.40.18.png
「Success」と表示される。
スクリーンショット 2020-02-16 10.40.21.png

キャンセル時は「Unauthorized」と表示される。
スクリーンショット 2020-02-16 10.40.08.png

終わり。

curlコマンドで確認

ちなみにターミナルでcurlコマンド叩いても確認できる。

$ curl -u user:pass http://localhost:3000

今回はビューをレンダリングしてるのでhtmlが吐かれる。

<!DOCTYPE html><html><head><metacharset="utf-8"/><title>Success</title></head><body><h1>Success</h1></body></html>

誤ったユーザー名、パスワードを指定したり、userオプション(-u)を指定しなかった場合はUnauthorizedが返ってくるだけ。

Unauthorized

node.js(express)のbody-parserを理解する

$
0
0

bodyParserとは、

HTML(ejs)のformのinputに入力された値を受け取れるようにするものです。

例↓

<formaction="/"method="post"><p><inputtype="text"name="message"><inputtype="submit"value="送信"></p></form>

この例の場合は、

<inputtype="text"name="message">

の値を取得します。

inputの値をどう取得するの?

inputにnameを指定して、javascriptで

req.body.[inputnameに指定した値]

というように書く事でinputの値を受け取れるようになります。

上の例のnameにmessageを指定した例ですと、下のように書けば値を受け取れます。

req.body.message

body-parserをインストールしなければ、上のコードを書いてもエラーになります。

body-parserを使った簡単なアプリの例

html(ejs)でフォームを作成します。

index.html
<!DOCTYPE html><htmllang="ja"><head><metahttp-equiv="content-type"content="text/html"charset="UTF-8"><title><%=title%></title><linktype="text/css"href="./style.css"rel="stylesheet"></head><body><head><h1><%=title%></h1></head><divrole="main"><p><%-content%></p><formaction="/"method="post"><p><inputtype="text"name="message"><inputtype="submit"value="送信"></p></form></div></body></html>

このフォームを送信したら(input type="submitのボタンを押したら)、actionへ移動します。

index.js
varexpress=require('express');varejs=require("ejs");varapp=express();app.engine('ejs',ejs.renderFile);app.use(express.static('public'));varbodyParser=require('body-parser');app.use(bodyParser.urlencoded({extended:false}));// ※トップページapp.get('/',(req,res)=>{varmsg='This is Express Page!<br>'+'※メッセージを書いて送信して下さい。';res.render('index.ejs',{title:'Index',content:msg,});});// ※POST送信の処理app.post('/',(req,res)=>{varmsg='This is Posted Page!<br>'+'あなたは「<b>'+req.body.message+'</b>」と送信しました。';res.render('index.ejs',{title:'Posted',content:msg,});});varserver=app.listen(3000,()=>{console.log('Server is runnning!');})

javascriptでpost送信されたあと、
body-parserを使ってreq.body.messageで値を受け取り、
それを表示させています。

作ったアプリ↓

スクリーンショット 2020-02-16 21.37.22.png

”ありがとう”という値を送信しました。
それを受け取っていることが分かります。

終わりに

参考にした本↓
https://www.amazon.co.jp/Node-js%E8%B6%85%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798055220/ref=asc_df_4798055220/?tag=jpgo-22&linkCode=df0&hvadid=295723231663&hvpos=1o1&hvnetw=g&hvrand=2502203059216170870&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009243&hvtargid=pla-526606012765&psc=1&th=1&psc=1

の212ページ

node.js(express)のパーシャルとは何なのか

$
0
0

パーシャルとは

レンプレート内から更に読み込んで使われる、テンプレート内の小さな部品を”パーシャル”と呼びます。
node.jsのテンプレートはejsなどがあります。

パーシャルの例

<tr><th><%=key%></th><td><%=val%></td></tr>

これはテーブル内の一部の例で
これをテンプレートで読み込んで使います。

パーシャルの使い方

テンプレート内で下のように書いて読み込みます。

<%-incluede('パーシャルのファイル名',{受け渡す値})%>

実際の例

<%-incluede('data_item',{key:key,val:data[key])%>

TypeError: validator is not a functionが解決しない

$
0
0

とりあえず初心者向けの書籍を一冊購入。

とにかく写経でもいいから一通りのアプリを作成したいと動きをメモしつつとりあえず書き上げた。
さあ動かしてみよう

しかしコンソールログに出たのは・・・

TypeError: validator is not a function

いやいや読み込んでますけど汗

varvalidator=require('express-validator');// ~~~~~~// (中略)// ~~~~~~app.use(validator());

※本当はvar使っちゃいけないけどとりあえず写経なので、
あとサンプルのソースと比較しやすいためこのままにしてます

どこが違うのか比較しよう

ソースは同じ汗

となると、インストールしたモジュールのバージョンがちがうのか?

以下自分のpackage.json

package.json
{"name":"mini-board-2","version":"0.0.0","private":true,"scripts":{"start":"node ./bin/www"},"dependencies":{"bookshelf":"^1.1.0","cookie-parser":"~1.4.4","debug":"~2.6.9","ejs":"~2.6.1","express":"~4.16.1","express-session":"^1.17.0","express-validator":"^6.4.0","http-errors":"~1.6.3","knex":"^0.20.9","morgan":"~1.9.1","mysql":"^2.18.1"}}

以下サンプルのpackage.json

package.json
{"name":"mini-board-2","version":"0.0.0","private":true,"scripts":{"start":"node ./bin/www"},"dependencies":{"bookshelf":"^0.13.3","cookie-parser":"~1.4.3","debug":"~2.6.9","ejs":"~2.5.7","express":"~4.16.0","express-session":"^1.15.6","express-validator":"^5.2.0","http-errors":"~1.6.2","knex":"^0.14.6","morgan":"~1.9.0","mysql":"^2.15.0"}}

全然違う

バージョンをそろえて、npm install

通りました

$ npm start

> mini-board-2@0.0.0 start C:\Users\user\Desktop\mini_board_2
> node ./bin/www

GET / 302 118.593 ms - 56
GET /users 200 179.510 ms - 1242
GET /stylesheets/style.css 200 22.919 ms - 753
GET /favicon.ico 302 5.750 ms - 28
GET /users 304 9.479 ms - -

原因は?

最初にいろんなパッケージをインストールする際、最新のパッケージをインストールしたためではないか。
本自体は昨年の5月に改訂されたがいろいろググるうちに自分と同じエラーを質問している方がいて、書き方が古いですよーみたいなことを言われている。
たしかに、ほかに出していたpaginationのエラーも今はこういう書き方ですよーみたいな内容の文章があった。

最新のNode.js情報を追いかけなければ。

参考文献・サイト

・掌田 津耶乃 (2019/5/10)Node.js超入門[第2版] 秀和システム
https://qiita.com/maitake9116/items/7825d90c09f3e2f87dea
https://stackoverflow.com/questions/56711444/typeerror-validator-is-not-a-function-after-installing-and-requiring-express

追伸

terat●ilで同じエラー質問したやりとりみてて安定のググレカス。
このサイトで回答するひとってこういうタイプ多くない?

htmlを介さずにjsonから自動でコンテンツを作って更新するスクリプト

$
0
0

こんにちは、wattak777 です。

最近、必要に迫られてNode.jsを嗜むようになったのですが、とある時に「jsonで記載された元データをロードしてコンテンツとして表示し、ユーザがそのコンテンツを更新するとそのjsonを更新する」みたいなことをしようとしてNode.jsでソース一本で出来ないか?と考えたサンプルです。

ファイル構成は以下。
+
├ server.js
└ db.json

server.js
varexpress=require('express');varurl=require('url');varapp=express();varjson_write={param1_label:'',param1_value:'',param2_label:'',param2_value:''}app.get('/update_param',function(req,res){console.log(" GET");varurl_parse=url.parse(req.url,true);console.log("  search:%s",url_parse.search);json_write.param1_label=url_parse.query.param1_label;json_write.param1_value=url_parse.query.param1_value;json_write.param2_label=url_parse.query.param2_label;json_write.param2_value=url_parse.query.param2_value;varjson=JSON.stringify(json_write,null,'');varfs=require('fs');fs.writeFile('./db.json',json,err=>{if(err){res.sendStatus(500);}else{res.sendStatus(200);}});});constsetJson_JS='<script type="text/javascript">\n'+'  function setJson() {\n'+'    var sethttp = new XMLHttpRequest() ;\n'+'    var url_str = "http://自らのIPアドレス:50000/update_param?" ;\n'+'    var elem = document.getElementById("param1_label") ;\n'+'    var url_str = url_str + "param1_label=" + encodeURI(elem.innerText) + "&" ;\n'+'    var elem = document.getElementById("param1_value") ;\n'+'    var url_str = url_str + "param1_value=" + encodeURI(elem.value) + "&" ;\n'+'    var elem = document.getElementById("param2_label") ;\n'+'    var url_str = url_str + "param2_label=" + encodeURI(elem.innerText) + "&" ;\n'+'    var elem = document.getElementById("param2_value") ;\n'+'    var url_str = url_str + "param2_value=" + encodeURI(elem.value) ;\n'+'\n'+'    sethttp.open("GET", url_str) ;\n'+'    sethttp.send() ;\n'+'  }\n'+'</script>\n';consttempl_input='<p><input type="button" value="close" onClick="Javasctipt:history.back()"></p>\n'+'<p><input type="button" value="send" onClick="setJson()"></p>\n';app.get('/view_param',function(req,res){console.log(" GET");varfs=require('fs');varreadJson=fs.readFileSync('jsondb/db.json');constjsonObj=JSON.parse(readJson);varhtml_text='<p><span id="param1_label">';html_text+=jsonObj.param1_label;html_text+='</span>:<input type="text" size="100" name="param1" id="param1_value" value="';html_text+=jsonObj.param1_value+'"></p>\n';html_text+='<p><span id="param2_label">';html_text+=jsonObj.param2_label;html_text+='</span>:<input type="text" size="100" name="param2" id="param2_value" value="';html_text+=jsonObj.param2_value+'"></p>\n';html_text+=templ_input;res.writeHead(200,{'Content-Type':'text/html'});res.write(setJson_JS+html_text);res.end();});varserver=app.listen(50000,function(){console.log("listening at port %s",server.address().port);});
db.json
{"param1_label":"param1_label","param1_value":"param1 def value.","param2_label":"param2_label","param2_value":"param2 def value."}

これを使ってnodeで起動させ、ブラウザより、
http://立てたIPアドレス:50000/view_param
とすると画面が表示され、更新後にjsonが更新されることが出来ました。

EXCELファイルをバックアップし、DBに格納【Node.js Express】

$
0
0

エクセルファイルをアップロードしたら、バックアップを作成し、中身のデータをデータベースに登録するAPIを作りました。また似たような作業することになりそうなので、メモ。
Node.jsとExpress、データベースはMysqlを使用

使うもの

1)multer
アップロードされたエクセルファイルを特定のファイルにコピーする。
※ただし、multipart/form-dataのみ
https://www.npmjs.com/package/multer

インストール:
npm install --save multer

2)sheetjs
エクセルファイルの書き換え等ができる。
今回はエクセルファイルをJSONに変換するのに使用した。
https://www.npmjs.com/package/xlsx

インストール:
npm install --save xlsx

実装

constexpress=require('express')constapp=express()constmodels=require('../model/models')constxlsx=require('xlsx')constmulter=require('multer')conststorage=multer.diskStorage({//保存先を指定destination:function(req,file,cb){cb(null,'./uploads/excels')},//ファイル名の先端に日付をつけて保存filename:function(req,file,cb){cb(null,newDate().toISOString()+file.originalname)}})constfileFilter=(req,file,cb)=>{//ファイルの拡張子を確認 とりあえず、excelとspredsheetを。if(file.mimetype==='application/vnd.ms-excel'||file.minetype==='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'){cb(null,true)}else{cb(null,false)}}constupload=multer({storage:storage,limits:{//アップロードできるファイル数files:1,//ファイルサイズ(2MB@)fieldSize:2*1024*1024},fileFilter:fileFilter})app.post('/upload',upload.single('ファイル名'),async(req,res)=>{try{constsheet=xlsx.readFile('./uploads/excels/'+req.file.filename,{cellDates:true})constworkingSheet=sheet.Sheets['シート名']constdata=xlsx.utils.sheet_to_json(workingSheet)vardataArray=[]//カラム名varfieldNames=['date','name','tel']for(variindata){//String型を明記しないとエラーになったので。 //エクセルのカラム名?(カラム最上セルのテキスト)日本語なのがアレなので良い方法あれば教えて欲しいです。//電話番号は -(ハイフン) を抜き取る。varinsertData=`(${JSON.stringify(data[i].日付)}, ${JSON.stringify(data[i].名前)}, , ${data[i].電話番号.replace(/-/g,'')})`dataArray.push(insertData)}consttableName='テーブル名'//毎回データを総入れ替えする仕様のため、全データ消すconstareDeleted=awaitmodels.deleteAll(tableName)if(!areDeleted)returnres.status(500).json({message:'エラーが発生!'})//データを挿入constareInserted=awaitmodels.insertData(tableName,fieldNames,dataArray)if(!areInserted)returnres.status(500).json({message:'エラー発生!'})returnres.status(200).json({message:'成功!'})}catch(err){console.log(err)returnres.status(500).json({message:'エラーが発生!'})}})

モデル抜粋

model.js
constmysql=require('mysql2')constmodels={}// Setup databaseconstconnection=mysql.createConnection({host:ホスト,user:ユーザー,password:パスワード,database:データベース名,port:ポート番号,multipleStatements:true//複数のコールを有効化})connection.connect((err)=>{if(!err)console.log('接続成功')elseconsole.log('接続失敗 : '+JSON.stringify(err,undefined,2))})models.deleteAll=(tableName)=>{returnnewPromise((resolve,reject)=>{connection.query(`DELETE FROM ${tableName}`,(err,results)=>{console.log(results)if(err)returnreject(err)elsereturnresolve(results)})})}models.insertData=(tableName,fieldName,insertData)=>{returnnewPromise((resolve,reject)=>{connection.query(`INSERT INTO ${tableName} (${fieldName}) VALUES ${insertData}`,(err,results)=>{console.log(results)if(err)returnreject(err)elsereturnresolve(results)})})}module.exports.models=models

2020年版 Node.js+Reactのインストール

$
0
0

1.概要

MacbookにNode.jsとReactをインストールし、アプリを開発するための環境を構築するための手順について。

2.前提条件

事前作業

作業日時

  • 2020年2月

環境

  • MacBook Pro
  • macOS Catalina

ソフトウェアのバージョン

  • nodebrew 1.0.1
  • Homebrew 2.1.11
  • yarn 1.21.1

3.インストール手順

Node.jsをインストールする

以下の流れでインストールする。

  1. Homebrewのインストール
  2. nodebrewのインストール
  3. Node.jsのインストール

Homebrewのインストール

以下のコマンドでHomebrewをインストールする。

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

nodebrewのインストール

Homebrewを使用してインストールする。

brew install nodebrew

インストール後は以下コマンドでnodebrewのバージョンが確認できます。

nodebrew -v
nodebrew 1.0.1

node.jsのインストール

$ mkdir -p ~/.nodebrew/src

最新版を取得する際は

$nodebrew install-binary latest

安定版を取得する際は

$nodebrew install-binary stable

インストールされたnodeを有効化

$nodebrew ls

上記、コマンドでインストールされたバージョンが一覧できる。

v7.1.0

current: none

インストール直後はcurrent: noneとなっているため、必要なバージョンを有効化する。

$nodebrew use v7.1.0

もう一度nodebrew lsを試すと

v7.1.0

current: v7.1.0

v7.1.0が設定されました。

参考記事

ちなみに、Windowsの場合は、以下のインストーラでインストール可能。

https://nodejs.org/en/download/

yarnのインストール

npmの代わりのパッケージマネージャ。
yarn addでパッケージをインストールできる。

$npm install-g yarn

Reactのアプリの作成

以下コマンドでReactアプリのフォルダが作成される。
Typescriptで開発を行うため、--template typescriptのオプションを設定する。

npx create-react-app <app name>--template typescript

各種ライブラリのインストール

作成したフォルダに移動して、各種ライブラリをインストールする。

$cd my-react-app

Reduxのインストール

プロジェクトルートフォルダで以下コマンドを実行する。
reduxはreduxそのもの、react-reduxはreactとreduxをつなぐライブラリです

$ yarn add redux react-redux typescript-fsa typescript-fsa-reducers

typescript-fsaはAction側、typescript-fsa-reducersがReducer側で利用するライブラリです。

  • typescript-fsa → ActionCreatorを簡単に生成するライブラリ
  • typescript-fsa-reducers → Reducerを簡単に作成するためのライブラリ

開発ツール(redux-devtools)のインストール

redux-devtoolsは、Reduxで開発する際に利用できる便利な開発ツールで、アクション実行時のstoreの状態を確認するのに利用する。
これに加えてChromeの拡張機能でRedux DevToolsを追加するとツールでRedux上のStoreの状態が確認でき、非常にデバッグしやすくなります。

yarn add --dev redux-devtools

Prettierのインストール

整形ツールのPrettier(-Dで開発版のみにインストールされる)
設定はこちら
https://qiita.com/awakia/items/3a05edfa135762d7952c

#yarn add -D prettier

Eslintのインストール

#yarn add -D eslint  \
   @typescript-eslint/eslint-plugin  \
   eslint-plugin-prettier  \
   eslint-config-prettier

Material-ui

Materialデザインを利用するためのパッケージ。
タッチ、タップ、クリックなどのイベントを使うためにreact-tap-event-pluginのインストールも必要。

#yarn add material-ui react-tap-event-plugin 
#yarn add @material-ui/core @material-ui/icons

react-router-dom

react-routerの代わりにConnected React Routerを使う。
URLに応じて、表示するコンテンツを変更するルーティングを行うためにインストールする。

# yarn add react-router-dom connected-react-router
# yarn add -D @types/react-router-dom

date-fns

dateオブジェクトのライブラリ。
momentより軽いらしい。

$ yarn add date-fns

4.コマンド

ローカルでサーバーを起動する

以下コマンドでローカルでサーバーを起動できる。
http://localhost:8080/でアクセスできる。

$ yarn start

ビルドする

$ yarn build

5.終わりに

  • firebaseの設定は別途。

以上。

nodebrewによるNode.jsのインストールとバージョン管理

$
0
0

概要

ローカル環境がごちゃごちゃしていたので一度全てアンインストールしてので再インストールした記録。

環境

macOS Catalina 10.15.3
zsh(bashを使用している場合は「.zshrc」を「.bashrc」に読み替えられる。)
homebrew

手順

1.Node.jsの確認とnodebrewのインストール

$node -v# nodeの存在の確認 #バージョンが表示されたら先にアンインストールする。  command not found: node

$brew install nodebrew # nodebrewのインストール$nodebrew -v# nodebrewインストールの確認  nodebrew 1.0.1

2.PATHの設定

$nodebrew setup # PATHの部分をコピーFetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH# <=ここをコピー========================================

$echo'# nodebrew'>> ~/.zshrc \ # PATHを通す$echo'さっきのコピーを貼り付け'>> ~/.zshrc \$ #例) echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc \$vim .zshrc # PATHの確認。「:q」でvimを終了。#nodebrew
export PATH=$HOME/.nodebrew/current/bin:$PATH

3.Node.jsのインストール

$nodebrew install-binary stable # Node.js安定版(stable)のインストール。$nodebrew list # インストールの確認v13.8.0
v12.16.0
current: none

$nodebrew use 12.16.0 # バージョンの変更。複数ある場合はインストール時に表示されたバージョン。use v12.16.0

$nodebrew ls# 使用バージョン(current)の確認。v12.16.0
v13.8.0
current: v12.16.0

$node -v# バージョン確認v12.16.0

$npm -v# node.jsと一緒にインストールされるパッケージ管理ツール。6.13.4

4.使用しないバージョンのアンインストール(任意)

$brew uninstall 13.8.0 # アンインストールv13.8.0 uninstalled

$nodebrew ls# アンインストール確認。v12.16.0
current: v12.16.0

参考

【2018年版】macのhomebrewでnodebrew入れてからnode.jsを入れるまで
NodebrewでNodeをインストールする

N予備校の教材縛りで割といけてるWebサービスが作れた!

$
0
0

N予備校のプログラミング入門コースが本格的だと最近話題ですね!
というわけでN予備校入門コースの技術を使って、1からWebサービスを作ってみました!
(ゆるい縛りプレイみたいな感じ)

作ったサービス

https://www.meish.me/
バーチャルキャラクターのためのプロフィール登録サービスです

開発は1人(テスターさん除く)で、開発期間(リリースまで)は2ヶ月ちょいでした。
プログラミング初心者ではないですが、Webサービスをリリースするのは初めてです。

リリースから2日で400名以上のバーチャルなキャラクターさんが登録してくれていて、わりとちゃんとしたWebサービスになってるかなと思います!

TOPページ
image.png
プロフィールページ
image.png

GitHub
https://github.com/OkuriSae/Meish

使った技術

必要に迫られない限り、N予備校のプログラミングコースで学べる内容で進めました!
プログラミング入門コースを終えた時点で以下の環境ができてるので、すぐに開発が始められて便利 (◦ˉ ˘ ˉ◦)

今回使った技術のうち、N予備校で教えてくれるもの

  • 第1章
    • VSCode, JavaScript, HTML, CSS, Tweetボタン
  • 第2章
    • VirtualBox, Vagrant, Ubuntu, git, GitHub
  • 第3章
    • Node.js, Pug, PostgreSQL, sequelize, Bootstrap4, Heroku
    • 脆弱性対策(XSS,CSRFとか)
  • 第4章
    • Express, webpack, Passport
  • 特別付録コース
    • TwitterAPI

今回使ったもので、N予備校に無いもの

  • Google Domain
    • meish.me のドメインを取得しました
  • Amazon S3
    • アップロード画像の保存先に使いました
    • Herokuでは画像保存ができないので仕方なし!
  • Jimp(画像処理のnpmモジュール)
    • 画像の圧縮やリサイズに使いました
    • 画像そのまま保存するとデータ転送量が大変…
  • OGP/TwitterCard
    • ツイッターやSlackにURLを貼ると画像が展開されるアレ!
    • HEADにメタタグを入れるだけなのでカンタン

本番リリースまでに追加でやったこと

N予備校の教材に書いてあること以外で、本番リリースまでにやったことです!

Twitterのデベロッパー申請した

GitHubのアプリケーション登録と違い、Twitterはデベロッパー申請が必要です!
Google翻訳を使いながら英語で申請文を作りましたが、N予備校の特別付録にやり方が載ってたので助かり。審査とかで1週間ぐらいかかりました。

Google Domainでmeish.meのドメインとった

なんとなくGoogle Domainでとっちゃったけど、DMMやお名前ドットコムの方が初年度すごい安いのでそっちがいいかも。meish.meのドメインは年2,600円ぐらいかかるみたいです。(料金はドメインによって違います)
Herokuに転送するためにはサブドメインを切る必要があるみたいなので、www.meish.meのサブドメインを作りました(サブドメインを作るのはタダ)。

HerokuのDynoPlanをHobbyにしてSSL設定した

独自ドメインを使ったり、SSL証明書の設定をするのはHobbyプランからみたいでした。Hobbyプランは$7.00/月です。HobbyプランだとサーバーのリージョンがUSとかUKしか選べないので、ちょっと遅いことがあるかも。

meish.meのドメインからHerokuのドメインに転送する設定やった

meish.meのドメインでHerokuに繋がるようにする設定をしました。
Heroku側にドメインを登録するとDNS Targetとやらをもらえるので、Googleドメイン側でそのDNS Targetに転送するように設定しました。

HerokuのPostgreSQLのプランをHobby Basicにした

HerokuのPostgreSQLアドオンはdevプランだと合計5万件しかデータを作れないみたいなので、1千万件までデータを保存できるHobby-Basicプラン($9.00/月)にしました。サービスの種類や設計によってはdevプランでも全然大丈夫かも。

Herokuで画像保存してたらデプロイや再起動のたびに画像が消えちゃったのでAmazon S3にした

Herokuはデプロイのタイミングや1日1回行われるサーバーの再起動で、サーバーをコンテナごと作り直すので、ローカルに作ったファイルなどが消えてしまうみたいです。
裏技としてデータベースにBlobで画像をおいておく方法があるみたいですが、今回は素直に外部ストレージ(Amazon S3)を使うことにしました。

OGPとかTwitterCard用のメタタグをいれた

URLをTwitterとかで拡散してもらいたいので、画像展開されるOGP/TwitterCardの設定をしました!HTMLのHEADスペースにOGPやTwitterCard用のメタタグを入れるだけなので簡単でした。
OGPのパワー➡︎ https://twitter.com/hashtag/Meish?f=live

おわりに

今年の1月にN予備校のバーチャル特別講師として招いていただいたので、そのお礼も兼ねて(?)N予備校でこんなことできるよーっていうのをやってみました!

またちょうどN予備校の内容でWebサービスを作るコンテストが開催中で、高校生の方やN予備校生の方がたくさん参加しているようなので、この記事がお役に立てば嬉しいです!(応募期間終わっちゃったけど…)

小栗さえ(青葉さえ)
Twitter: @OkuriSae

Verdaccioを使って簡単local npm registryを構築する

$
0
0

ローカルにnpm registryを構築する方法はいくつかありますが、結構有名らしいVerdaccioを使って構築するための備忘録
ブログとかQiitaにも記事はあるんだけど肝心な設定が抜けていたりしていたのでその辺も載せておきます

目次

  1. 構築環境
  2. 必要なパッケージのインストール
  3. verdaccioの設定
  4. 一旦verdaccioを起動させる
  5. verdaccioをサービスに登録する
  6. registryを追加する
  7. npm userを作成する
  8. local registryに公開・削除する

構築環境

  • Linux mint 19.1
  • node.js 8.10.0
  • npm 3.5.2

必要なパッケージのインストール

npmのインストール

Verdaccioはnpm packageの一つなので大前提としてnpmをインストールしておく必要があります
(インストール済みであればスキップ)
sudo apt update && sudo apt install -y npm

verdaccioのインストール

npmをインストールし終わったらverdaccioをグローバルインストールします
sudo npm i -g verdaccio

verdaccioの設定

標準のサービスファイルをsystemdにコピーする

sudo cp /usr/local/lib/node_modules/verdaccio/systemd/verdaccio.service /etc/systemd/system/

Server Configuration・Verdaccioを参考にしていたのですが、どうやらLinux mintではサービスファイルの格納ディレクトリやsystemdディレクトリが違っていたので上記の様になりました
ディストロによって公式と同じディレクトリにあり、変更する必要がないかもしれないので適宜読み替えてください

公式のコマンドはこちら

sudo cp /usr/lib/node_modules/verdaccio/systemd/verdaccio.service /lib/systemd/system/

コピーしたサービスファイルを修正する

このままだとサービスを実行してもエラーを吐かれてしまうので下記画像の様に書き換えます

verdaccio_service.png

ExecStartに指定している実行ファイルの場所やconfigファイルの場所を変更しています
(verdaccioの実体がディストロによって変わるのか分かりませんが、Linux mintでは上記ディレクトリに配置されています)

configについては後程verdaccioを実行すると、ホーム直下の.config/以下に自動生成されるのでそれを割り当てています

一旦verdaccioを起動させる

verdaccioと打ち込んで起動させます
すると先ほども書いたように$HOME/.config/verdaccio/config.yamlが生成されているかと思います

configファイルを修正する

configファイルの生成を確認したら停止させるか別のターミナルを開き、以下の様に修正します
(ユーザー名がgn5rなので適宜読み替えてください)

$HOME/.config/verdaccio/config.yaml
storage:/home/gn5r/.local/share/verdaccio/storageplugins:./pluginsweb:title:Verdacciostorage:/home/gn5r/.local/share/verdaccio/storageplugins:./pluginsweb:title:Verdaccioauth:htpasswd:file:./htpasswduplinks:npmjs:url:https://registry.npmjs.org/local:url:http://localhost:4783/packages:'@*/*':# scoped packagesaccess:$allpublish:$authenticatedunpublish:$authenticatedproxy:local'**':access:$allpublish:$authenticatedunpublish:$authenticatedproxy:localserver:keepAliveTimeout:60middlewares:audit:enabled:truelisten:0.0.0.0:4783

local registry のproxyを追加

これがキモでlocal registryと公式registryを両立させるためにproxyを追加

uplinks:
  local:
    url:  http://localhost:4783/

npm package問い合わせ先をlocal優先にする

この部分でパッケージ問い合わせをlocal registry優先にしています。見つからなかったら公式registryに問い合わせる感じ

packages:
  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: local

  '**':
    access: $all

    publish: $authenticated
    unpublish: $authenticated

    proxy: local

外部アクセスを受け付けるにはこれが必須

この設定をしないと開発マシンからverdaccioサーバーのwebページにアクセスできなかったり不便なのでほぼ必須です
(最初ちゃんと調べずにやっていたらエラーが出て、それをTwitterでボヤいたら公式が教えてくれました)
また、ポートを変えたい場合もここで設定できます(デフォルトは4873)

listen: 0.0.0.0:4783

verdaccioをサービスに登録する

起動・自動起動の設定

以下3つを実行しverdaccioを起動&自動起動を有効にする
sudo systemctrl daemon-reload
sudo systemctrl start verdaccio
sudo systemctl enable verdaccio

動作確認

ポートを変えていなければブラウザを開いてIPアドレス:4873にアクセスするとverdaccioのwebページが表示されればOK

verdaccio_web.jpg

(画像ではポートを変えてあるので4783にアクセスしています)

registryを追加する

ここにも書いてありますが、構築したregistryを追加する方法はいくつかあるようです

パッケージ毎に分けたい場合

パッケージ(プログラム)以下のpackage.jsonがあるディレクトリの.npmrcregistry=http://IPアドレス:4783を追記(無ければ作成)

グローバルに設定する場合

npm set registry http://IPアドレス:4783を実行

npmコマンド実行時に毎回registryの引数を渡す場合

各npmコマンド実行時に--registry http://IPアドレス:4783を付与

npm userを作成する

新規作成の場合

npm adduser --registry http://IPアドレス:4783
ユーザー名とパスワード、Emailを入力すれば完了(パスワードは1回入力)

ログインする場合

別マシンで作成したユーザーを使いたい場合は
npm login --registry http://IPアドレス:4783

local registryに公開・削除する

設定でlocalを優先にしているので、試しに--registryを付けずにpublish、unpublishしてみたけど途中で止まってしまったので、--registryは必須かと思います
(多分公式のregistryにも投げているのかもしれない。公式のaccessTokenは無いので認証部分で止まっているのかと推測しています)

公開

package.jsonがあるディレクトリで
npm publish --registry http://IPアドレス:4783

公開されているパッケージを削除する

npm unpublish --force パッケージ名 --registry http://IPアドレス:4783

nodemailer で 複数宛メール作成時の注意

$
0
0

nodemailer を使って宛先が複数ある時の設定メモ。
envelopeを設定していてはまりました。
ヘッダーがどうなってるとかきちんとした確認はとってません。

  • nodemailer v6.3
send-mail.js
varmailer=require('nodemailer');consttransporter=mailer.createTransport({sendmail:true,newline:'windows',logger:false})transporter.sendMail({from:'me@example.com',to:'to@example.com',cc:'cc@example.com',envelope:{from:'me@example.com',// ここのtoは実際に送信する宛先。// 複数宛先がある場合はすべて含める必要がある。// ✖ to: 'to@example.com',to:['to@example.com','cc@example.com'],},subject:'THIS IS A TEST MAIL.',text:'HELLO! nodemailer!!'})
Viewing all 8837 articles
Browse latest View live