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

同僚の作成したdockerコンテナをbuildする際にコケたポイントメモ

$
0
0

初めに

同僚が作成してくれたnodeのdockerコンテナをbuildする際に何点かコケたポイントがありましたのでメモしてみます。
同様のケースで嵌っている方がいたら参考になれたらと思います!

前提

コンテナを作成してくれた人のPC
Mac Pro
こちらの環境では一発docker-compose up --buildを流せばbuildとupが通る
私のPC
Windows10 Pro
npm --version
6.14.5
数回コケる
コンテナの構成
Node→image: node:latest
MySQL→image: mysql:5.7
Nginx→image: nginx:latest

Cannot create container for service mysql: Conflict

image.png

既にmysqlという名のコンテナが存在するからコンフリクトするぞというエラーですね。
他のnodeやnginxも同様に発生しました。
良くデフォルトの名前でコンテナ名をつける事が多いので、プロジェクト毎にコンテナ名をAservice_mysqlとかユニークな名前を指定するとスムーズかも知れませんね。
既に存在したmysqlコンテナはもう殆ど動かないプロジェクトのものでしたので、

docker rm コンテナID

削除しました。

npm ERR! enoent ENOENT: no such file or directory, rename '/src/node_modules/constantinople' -> '/src/node_modules/.constantinople.DELETE'

constantinopleが見つからないと怒られました。
constantinopleは定数の評価で使用するモジュールのようですね。
https://www.npmjs.com/package/constantinople
使用しているnpmのバージョンを最新にアップデートし解決しました。
6.14.5→6.14.8

npm install-g npm

仮にこの方法で直らなかった場合、単にnpm install時にmoduleの中身が破損しているパターンや、package-lock.jsonが各モジュールを良くない組み合わせのバージョンで固定しているパターンが考えられるので、
node_modulesの中身をすべて削除する
package-lock.jsonを削除して再度npm installする
を試してみてください。

npm ERR! Maximum call stack size exceeded

Nodeの最大コールスタックサイズを超えると発生するようです。
何でMacで起きないのにWindowsでは起きるんだ・・・。
npmのキャッシュを削除して再度buildしたら問題なく起動することができました。

npm cache clean --force

ちなみにnodeのstackサイズを変更すれば暫定的にこのコールスタックサイズ問題は解決しますが、
Nodeのパフォーマンスが悪くなる、最悪動かなくなる恐れがあるので止めた方が無難みたいです。。

最後に

同じ開発環境を瞬時に構築できるdockerは開発シーンに必須といえるツールになっていますね!
異なるOSでも動くのは素敵です。


マネーフォワードクラウド請求書から案件データぶっこ抜いて、Exmentに突っ込み簡易SFA/CRMを行う

$
0
0

SaaSのサービスをAPI同士で繋いで、業務改善アプリ的なものをつくるのが近年の趣味な筆者です。こんにちは。

マネーフォワードクラウド請求書 is 何?

マネーフォワードクラウドは、freeeと人気を二分する、中小企業やフリーランス向けのSaaS会計システムです。筆者は会社勤務の傍ら副業もしてますので、マネーフォワードクラウド確定申告というのを使って、毎年確定申告をしています。

また、関連サービスとしてマネーフォワードクラウド請求書(以下MFクラウド請求書)というサービスもあり、これを使って見積書と請求書を発行しています。

本稿とは別の余談になりますが、なかなかよく出来たサービスですので、請求書発行のサービスを探しておられる方は一度トライアルされてみてはいかがでしょうか。

Exment is 何?

Exmentは、OSSのWeb DBシステムです。SaaSではありませんが、筆者はLightSail + Dockerというクラウド環境で試験運用しています。

詳しくは下記記事をご参照ください。

また、詳しくは開発者の方の記事も参考になると思います。

APIで繋ぎこむ

MFクラウド請求書もExmentもAPIが用意されています。となると繋ぎこんでみたくなるのが人情というもの。

MFクラウド請求書はあくまで請求書発行のためのサービスなので、顧客マスタの概念はあっても、そこに対してのSFAやCRM的な機能はありません。

そこで、APIを使って、汎用的なDBシステムであるExmentにデータをコピーし、Exment上で色々やってみたいと思います。

この記事で実現できること

おおまかには以下です。

  1. 顧客ごとの売上額がわかる
  2. 顧客ごとの見込み額がわかる
  3. 案件ごとの見込み確度がわかる

これらの機能はMFクラウド請求書だけでは数値化・視覚化することができません。

セールスや経営の現場では、これらの情報が日々重要であり、セールス担当者は案件ごと、顧客ごとにこれらの情報を元に効率よく行動することができるようになります。それがSFAやCRMに求められる機能です。

SalesforceやkintoneといったSaaSを契約している企業であれば、それらでやってしまえばいいのでしょうけど、それらを契約できない規模の中小企業や私のような副業ワーカーであれば、Google App Scriptや、今回のExmentでやる、というのが現実的な方向性になると思います。

MF請求書のAPI仕様

MFクラウド請求書は、アクセストークンが必要で、リフレッシュトークンとともに発行される仕組みです。アクセストークン発行の仕組みは、下記記事をご覧ください。

APIドキュメントはこちらです。

ExmentのAPI仕様

ExmentのAPIは、3つの認証方式があります。今回は、API Key方式で行きたいと思います。MFクラウド請求書APIと同じく、アクセストークンとリフレッシュトークンが発行される方式です。

詳しくは下記記事を参考にしてください。

ExmentのAPIドキュメントはこちらです。

実装してみる

さて、いよいよ実装です。まずは、Exment側でカスタムテーブルを作ります。Salesforceでいうところのオブジェクト、kintoneで言うところのアプリ、そしてRDBMSで言うところの、テーブルに相当します。

「顧客リスト」カスタムテーブルの作成

カスタムテーブル作成の実際は、下記記事が詳しいので、ぜひご参照ください。

以下は、私が作成した「顧客リスト」のカスタム列設定です。RDBMSでいうところの、スキーマに相当します。カッコ内は、列種類(Exmentの用語、RDBMSでいうところの「型」に相当)

  • 顧客名(1行テキスト)

以上です。そっけないかもしれませんが、今回は顧客データベースをつくるのではなく、顧客ごとに売上や案件を管理したいだけなので、まずはこれだけにしておきます。

なお、idやcreated_atなどの列は自動的に追加されます。

「案件一覧」カスタムテーブルの作成

続いて、案件のカスタムテーブルを作成します。作成したカスタム列は、以下の通りです。この段階で、MFクラウド請求書API側のレスポンスのどのデータをどの列に突っ込みたいかを考えておきます。今回は、営業的な面で必要なものだけに絞りました。

  • 案件名(一行テキスト)※
  • 顧客名(選択肢(他のテーブルの値一覧から選択))※
  • 金額(税込)(通貨)※
  • 金額(税抜)(通貨)※
  • 確度(選択肢(値・見出しを登録))※
  • 除外(YES/NO)
  • 引き合い日(日付)
  • 見積作成日(日付と時刻)※
  • 見積書更新日(日付と時刻)※
  • 受注日(日付)

※印をつけた項目は、MFクラウド請求書側からのデータを受け付ける列になります。金額に税込と税別があるのも、MFクラウド請求書側がそのプロパティを持っているからですね。

また、確度の項目は、以下のように設定しました。(RDBMSでいうところのenum型ですね)

  • 1,アイデアレベル
  • 2,検討中
  • 3,見積発行
  • 4,商談中
  • 5,意思決定直前
  • 6,受注
  • 7,失注
  • 8,保留

1から6までは、受注確度です。営業用語でいうところの「顧客の温度感」ってヤツです。

アクセストークンを取得しておく

先程リンクしたMFクラウド請求書APIの記事を読んで、アクセストークンを取得します。

実際にはPostmanを使用して取得しました。

本当はプログラム化しておいたほうが良いのですが…

続いて、Exmentのアクセストークン取得です。こちらはプログラム化しました。普段筆者はJavaScriptに慣れ親しんでいるので、今回はNode.jsで書いています。ローカルで運用する前提です。

auth_exment.js
constaxios=require('axios')constfs=require('fs')constmoment=require('moment')constisExistFile=(file)=>{try{fs.statSync(file);returntrue}catch(err){if(err.code==='ENOENT')returnfalse}}constgetTokens=async(refresh_token)=>{letbody=''if(refresh_token){body={grant_type:'refresh_token',client_id:'df013b70-f1aa-11ea-8af3-c94ebdad7ad4',client_secret:'PSXXuKY8R3MrllA8EFSqPYtX3o3Oj7RP8s1cQBy1',refresh_token:refresh_token}}else{body={grant_type:'api_key',client_id:'df013b70-f1aa-11ea-8af3-c94ebdad7ad4',client_secret:'PSXXuKY8R3MrllA8EFSqPYtX3o3Oj7RP8s1cQBy1',api_key:'key_7B3faSNwMcU4MkJv3BZ8hF9LFHS7uQ',scope:'me value_read value_write'}}awaitaxios.post('https://db.uplift.company/oauth/token',body,{headers:{'Content-Type':'application/json'}}).then(res=>{constat=res.data.access_tokenconstrt=res.data.refresh_tokenconstexpires=res.data.expires_inconstexpireDay=moment().add(expires,'s').format()constdata={access_token:at,refresh_token:rt,expires_in:expireDay}fs.writeFileSync('./exment_tokens.txt',JSON.stringify(data))return{tokens:data}}).catch(err=>{console.log(err)})};(async()=>{lettokens={}if(isExistFile('tokens.txt')){// 既にトークンが存在するときtokens=JSON.parse(awaitfs.readFileSync('exment_tokens.txt'))// トークンが期限切れの時if(moment().isAfter(tokens.expireDay)){tokens={}getTokens(tokens.refresh_token)// リフレッシュトークンを使って、再取得tokens=JSON.parse(awaitfs.readFileSync('exment_tokens.txt'))}}else{// トークンが存在しない時getTokens()tokens=JSON.parse(awaitfs.readFileSync('exment_tokens.txt'))}console.log(tokens)returntokens})()

やってることは単純で、トークンの情報が書かれたテキストファイルが存在しなければ、初回トークン作成のPOSTをaxiosで投げます。そして取得したトークンの情報をローカルにテキストファイルで保存します。

顧客リスト取得とExmentに突っ込むプログラムを書いてみる

引き続き、顧客リスト取得のプログラムです。これもローカルで1回実行することが前提です。ホスティングして定期的に回すことは今のところ前提としていません(本当はそこまでやりたいですけど)

get-clients.js
constaxios=require('axios')constfs=require('fs');(async()=>{constendpoint='https://invoice.moneyforward.com/'constquery='api/v2/partners'awaitaxios.get(endpoint+query,{headers:{'Accept':"application/json",'Authorization':'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}}).then(async(res)=>{console.log(res)letitems=[]res.data.data.forEach(item=>{items.push({parent_type:'',value:{name:item.attributes.name}})})consttokens=JSON.parse(awaitfs.readFileSync('./exment_tokens.txt'))constexmentEndpoint='https://db.uplift.company/api/data/clients'awaitaxios.post(exmentEndpoint,{data:items},{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(res=>{console.log(res)}).catch(err=>{console.log(err)})}).catch(err=>{console.log(err.errors)})})()

MFクラウド請求書APIのアクセストークンがベタ書きです。いけませんね。良い子は真似しちゃダメです。dotenvなどで適切に処理しましょう。

それ以外の処理についてですが、

  1. 空の配列itemsにMFクラウド請求書APIをaxiosのGETで叩いた結果を収める
  2. itemsをペイロードに詰め、axiosのPOSTでExmentのAPIに投げる

というのがおおまかな流れです。itemsに突っ込む時は、ExmentのAPIドキュメントにあるカスタムデータ新規作成の記事を読んで

2020-09-10_13h54_04.png

上図は、突っ込んでみた結果です。うまく行きました。

案件(見積書)データを取得とExmentに突っ込むプログラムを書く。

引き続き、Node.jsです。

set-project.js
constaxios=require('axios')constfs=require('fs')const_=require('lodash')constmoment=require('moment');(async()=>{constendpoint='https://invoice.moneyforward.com/'constquery='/api/v2/quotes'awaitaxios.get(endpoint+query,{headers:{'Authorization':'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}}).then(async(res)=>{// console.log(res)    // クライアント名とIDの紐付けデータ作成consttokens=JSON.parse(awaitfs.readFileSync('./exment_tokens.txt'))constexmentClietnsEndopoint='https://db.uplift.company/api/data/clients'letclients=[]awaitaxios.get(exmentClietnsEndopoint,{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(res=>{res.data.data.forEach(item=>{clients.push({id:item.id,name:item.value.name})})})letitems=[]conststatus=async(item)=>{if(item.attributes.order_status==='default'){return3// 見積作成フラグ}elseif(item.attributes.order_status==='received'){return6// 受注フラグ}elseif(item.attributes.order_status==='failure'){return7// 失注フラグ}}constgetId=async(item)=>{constclient=_.find(clients,{name:item.attributes.partner_name})if(client){returnclient.id}else{return8// 取引停止}}constexclude=async(cond)=>{if(cond===8){returntrue}else{returnfalse}}for(leti=0;i<res.data.data.length;i++){letpdf=''awaitaxios.get(res.data.data[i].attributes.pdf_url,{responseType:'arraybuffer',headers:{'Authorization':'Bearer bbbf071b178fb29f1be08d41f3aad341ba599433f58d795d937266fd8d11dfda'}}).then(res=>{console.log('get pdf succeeded')pdf=newBuffer.from(res.data,'binary').toString('base64')}).catch(err=>console.log(err))constpayload={value:{name:res.data.data[i].attributes.title,client:awaitgetId(res.data.data[i]),amount:Math.floor(Number(res.data.data[i].attributes.total_price)),sub_amount:Math.floor(Number(res.data.data[i].attributes.subtotal)),reliability:awaitstatus(res.data.data[i]),exclude:awaitexclude(awaitgetId(res.data.data[i])),estimate_created_at:moment(res.data.data[i].attributes.created_at).format('YYYY-MM-DD HH:mm:ss'),estimate_updated_at:moment(res.data.data[i].attributes.updated_at).format('YYYY-MM-DD HH:mm:ss'),}}constexmentEndpoint='https://db.uplift.company/api/data/projects'awaitaxios.post(exmentEndpoint,{data:[payload]},{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(async(res)=>{// console.log(res.data[0].id)awaitaxios.post('https://db.uplift.company/api/document/projects'+'/'+res.data[0].id,{name:JSON.parse(res.config.data).data[0].value.name+'_見積書.pdf',base64:pdf},{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(res=>{console.log(res)}).catch(err=>{console.log(err.response.data.errors)})}).catch(err=>{console.log(err)})}}).catch(err=>{console.log(err)})})()

今回はちょっとばかり複雑なのと、axiosのコールバックがネストしまくってて、これもあまり良くありません。まあ、ワンオフ1回きりのコードなので、大目に見てください…

さて、各部の説明です。

set-project.js
constendpoint='https://invoice.moneyforward.com/'constquery='/api/v2/quotes'awaitaxios.get(endpoint+query,{headers:{'Authorization':'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}}).then(async(res)=>{// console.log(res)    // クライアント名とIDの紐付けデータ作成consttokens=JSON.parse(awaitfs.readFileSync('./exment_tokens.txt'))constexmentClietnsEndopoint='https://db.uplift.company/api/data/clients'letclients=[]awaitaxios.get(exmentClietnsEndopoint,{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(res=>{res.data.data.forEach(item=>{clients.push({id:item.id,name:item.value.name})})})

まず、axiosのGETでクライアント名一覧をMFクラウド請求書側から取得しています。まあ、Exment側から取っています… というか、これ、今気づいたんですが、Exmentに既に顧客テーブル作ったんだから、そっちから取得しても良かったんじゃね? って思いました… 次回からそうしよ。

次に初回axiosのコールバックで、またまたaxiosのGETでExmentのAPIを叩き、Exmentの顧客id(先程の挿入時に自動採番されている)を取得します。

最後に、両者を紐付けて、空の配列clientsにオブジェクトで突っ込みforEachで回します。

set-project.js
letitems=[]conststatus=async(item)=>{if(item.attributes.order_status==='default'){return3// 見積作成フラグ}elseif(item.attributes.order_status==='received'){return6// 受注フラグ}elseif(item.attributes.order_status==='failure'){return7// 失注フラグ}}constgetId=async(item)=>{constclient=_.find(clients,{name:item.attributes.partner_name})if(client){returnclient.id}else{return8// 取引停止}}constexclude=async(cond)=>{if(cond===8){returntrue}else{returnfalse}}

let items = []でからの配列を作成しています。

status関数は、それぞれの案件がどういう状態にあるかを定義する関数です。見積書があるということは、Exment側の「確度」項目では「3の見積作成」に相当するので、3を返すように。受注したものは6を、失注は7を返すようにしました。

getId関数は、顧客名から、Exmentの顧客idを取得できるようにするものです。lodashのfindメソッドを使って紐付けしています。

clientのid8番は、取引停止という特殊なクライアントです。MFクラウド請求書側で顧客データを削除してしまった場合は、顧客名が空欄になるので、Exment側で取引停止というクライアントを作成して対応させています。

exclude関数は、そもそも取引停止になっている案件の見積書は、除外しておきたいよね、という目的で作成しました。Exment側の「除外」列に相当します。クライアントid8番(つまり、取引停止)の場合は、除外列にtrueをセットします。

set-clients.js
for(leti=0;i<res.data.data.length;i++){letpdf=''awaitaxios.get(res.data.data[i].attributes.pdf_url,{responseType:'arraybuffer',headers:{'Authorization':'Bearer bbbf071b178fb29f1be08d41f3aad341ba599433f58d795d937266fd8d11dfda'}}).then(res=>{console.log('get pdf succeeded')pdf=newBuffer.from(res.data,'binary').toString('base64')}).catch(err=>console.log(err))constpayload={value:{name:res.data.data[i].attributes.title,client:awaitgetId(res.data.data[i]),amount:Math.floor(Number(res.data.data[i].attributes.total_price)),sub_amount:Math.floor(Number(res.data.data[i].attributes.subtotal)),reliability:awaitstatus(res.data.data[i]),exclude:awaitexclude(awaitgetId(res.data.data[i])),estimate_created_at:moment(res.data.data[i].attributes.created_at).format('YYYY-MM-DD HH:mm:ss'),estimate_updated_at:moment(res.data.data[i].attributes.updated_at).format('YYYY-MM-DD HH:mm:ss'),}}constexmentEndpoint='https://db.uplift.company/api/data/projects'awaitaxios.post(exmentEndpoint,{data:[payload]},{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(async(res)=>{// console.log(res.data[0].id)awaitaxios.post('https://db.uplift.company/api/document/projects'+'/'+res.data[0].id,{name:JSON.parse(res.config.data).data[0].value.name+'_見積書.pdf',base64:pdf},{headers:{'Authorization':'Bearer '+tokens.access_token}}).then(res=>{console.log(res)}).catch(err=>{console.log(err.response.data.errors)})}).catch(err=>{console.log(err)})}

いよいよ回していきます。ちょっとこの下りが複雑で冗長なのですが、大まかな流れとしては以下です。

  • 見積書PDFの取得 from MFクラウド請求書API
  • PDFデータをbase64エンコードして、空の変数に突っ込む
  • ペイロードに突っ込むデータを作る。金額は小数点でMFクラウド請求書APIから返ってくるので、Math.floorで丸めておく。
  • ExmentのAPIにaxiosのPOSTで1件のデータを突っ込む
  • そのレスポンスで返ってきた案件idを利用し、コールバックでもう一度axiosのPOSTで、今度は個別の案件に対してPDFを突っ込む

という処理をしています。

ExmentのAPI側は、複数案件をオブジェクトに収めて投げても受け付けてくれるのですが、これだとまとめて見積書PDFを収めることができません。そこで、1案件ごとに各フィールドを埋めるペイロードを用意し、POSTするようにし、axiosのコールバックとレスポンスで返ってくるid番号を利用してPDFだけあとから突っ込むということを実現できました。(いやー、コールバックとレスポンスって、こういうことのために使うんですねぇ。初めてこんなことしたので、勉強になりました)

なお、PDFを添付ファイルに添付する必要がなければ、複数案件まとめてAPIに投げちゃってもいいかな、と思います。その方が早いしリクエストも1回で済みますしね。

なお、PDF取得の処理にそれなりに時間がかかるので、Exment側(というかLaravel)のAPIコール上限には達しませんでしたが、必要があればExment側のAPIコール上限を緩和する設定などを施してください。

さて、無事データが突っ込めました。下記のようになってるでしょうか

2020-09-10_14h41_30.png

(実際にはこの画像、突っ込んだ後に編集しているので、厳密には異なる内容となるはずです)

Exment側でビューを作っていく

さて、ここまで来たらあとはNoCodeです。

Exmentにはビューという機能があります。デフォルトでは全件ビューというのになっていて、スプレッドシートのような外観をしています。

カスタムビューは、これらのデータを特定の条件に従ってフィルタリングしたり計算したりできる機能です。

フィルターだけなら全件ビューでもできるのですが、それを保存してユーザー間で共有できるところがビューの強みになります。

なお、計算ができるといっても簡易なものなので、複雑な処理をしたければ、APIからデータを取得して、処理するアプリケーションの開発をする必要があります。

さて、今回は以下のビューを作ってみます。

  1. 顧客別売上(今年)
  2. 顧客別売上(昨年)
  3. 見込み総額
  4. リードタイム

顧客別売上(今年)

案件情報画面に入り、右上にある「テーブル詳細設定」ボタンを押します。次に、出てきたモーダル内の「ビュー設定」
を押します。カスタムビュー設定という画面になるので、右上の「+新規」を押します。

またモーダルが出てくるので「集計ビュー新規作成」を押します。

「カスタムビュー設定 作成」という画面になるので、まずはビュー表示名のところを「顧客別売上(今年)」にします。

グループ列選択、集計列選択、データ表示条件は、以下のように設定しましょう。

2020-09-10_14h54_04.png

グループ列選択

集計したい軸を選択します。今回は顧客ごとに金額を集計するので、「顧客名」を選択しています。

集計列選択

集計したい値を選択します。今回は顧客ごとの金額ですので、「金額(税込)」と「金額(税抜)」を選択しました。

データ表示条件

列の値を使って、集計する対象を選定します。フィルタリングですね。

なお、今更なんですが、案件情報の列について、そのもたせた意味についてちょっと説明です。

除外

副業というか、受託をやってると「ちょっと見積書だけほしいんだけど(発注するかどうかは怪しいけど)」みたいなケースがあります。実際には、クライアントが補助金や助成金ありきのサイト制作を考えていて、その申請書類として見積書がほしいということでした。

そういう案件は受注するといいのですが、結構な金額で実際には受注しないということも多いので、営業戦略的には邪魔なデータとなってしまいます。

そんな案件は、手動で除外したいので「除外」というフラグを持たせています。また、取引停止の見積書もこの設定にしたのは先述の通りです。

確度

ここでは、確度ステータスは受注したか否かの判定に使っています。

受注日

これも営業戦略的には非常に重要で、月や四半期の売上目標の判定に必要です。今回は「今年」という期間なので、受注日が今年の範疇に含まれるものを設定しています。

出来たビューを見てみる

2020-09-10_15h08_21.png
こんな感じです。筆者は副業ということもあり、アクティブな取引先はそんなに多くないのですが、取引先の多い企業では、ここがずらずら~っと並んで、取引額の多い順番に並ぶということですね。

実際の営業現場では、取引先の金額が高い順番に、手厚い対応を取ることになると思います。

顧客別売上(昨年)

ということは、集計ビューを作った時に、データ表示条件で受注日を絞ったところを「去年」にすれば昨年の売上も出るわけですね。

一度作ったビューはコピーすることができるので、顧客別売上(今年)を複製して、最後の設定だけ変更してみましょう。

カスタムビュー設定画面の右端「操作」列の、2枚の紙が重なったアイコンをクリックすると、そのビューを複製することができます。

簡単ですね。

見込み総額

先程までは、売上、つまりこれまでの数値を可視化しました。営業的には「これから」の数値である「見込み総額」を算出したいと思います。

見込み総額ビューの設定は下記の通りです。

2020-09-10_15h22_46.png

売上の時と考え方は同じで、フィルタリングする条件で、確度を受注以前状態の値に設定しています。

リードタイム

リードタイムとは、引き合いをもらってから、受注に至るまでの期間のことです。見積り金額が増えれば増えるほどリードタイムが伸びる傾向にあります(それだけ大きいプロジェクトなので、顧客の意思決定にも複数人が関わる)。リードタイムが短い価格低めの案件を大量に獲るか、長めの案件を獲るかの基準づくりや、リードタイムかかりすぎの案件を洗い出すための判断基準として重要です。

今回の例では、引き合い日と受注日が設定されている案件が対象となります。リードタイムでは、カレンダービューという機能を使います。設定は下図の通りです。

2020-09-10_16h08_33.png

すると、こんな風にカレンダー表示してくれます。

2020-09-10_16h09_38.png

ま、正直この見せ方はベストではないと思うんですが、感覚的にリードタイムを掴むには良いのかな、と思いました。もう少し正確に把握するためには、やはりmomentとかで日付計算をして「○○万円以上の案件で△日以上リードタイムかかってたらアラートをだす」みたいなアプリを開発すべきでしょう。

これはひとつ、簡易的なやりかたということで。

まとめ

見積書・請求書に特化したサービスであるMFクラウド請求書を、汎用WebDBであるExmentと連携させて、簡易的なSFA/CRMにしてみました。SFA/CRMと名乗るにはまだまだ機能が足りないのですが、取り敢えず

  • どんぐらい稼げてるか
  • 太客はどこか
  • 案件獲得にどれくらいかかってるか

などは、これで洗い出せることになりました。

Google App Script + Googleスプレッドシートなどもいいですが、Exmentはカスタムビューの機能が強力だな、と思い今回は活用してみました。

Node.jsのくだりも、ただ今回インポートするだけの機能しかないので、今後は定期的に実行してMFクラウド請求書側のマスタとExment側のマスタが同期するような仕組みも作ってみたいと思います。

また、双方のAPIの仕組みもなんとなくわかっていただけたのではないでしょうか。双方ともに基本的なCRUDが出来るので、やりようによってはもっと高度な分析や日々の運用もこなせると思います。

これを機会に、MFクラウド請求書やExmentのユーザーが増えてくれると嬉しいです。

Sequelize で名前を指定してmigrationを実行する

$
0
0
sequelize db:migrate:status // でmigration nameの一覧表示
sequelize db:migrate --name <migration name> // migration 実行
sequelize db:migrate:undo --name <migration name> // rollback

Macのターミナルからhttp-serverコマンドを使って Webサイトを検証できるようにした

$
0
0

Macが突然壊れたので新しいMacを購入した。
現在、Webサイトを運営中でそれの更新作業を新しいMacでしようとした時に、ローカル環境で検証できないようになっていた。

具体的には、ターミナルでhttp-serverコマンドを打ってもエラーが出てくる。
新しいMacを購入してから環境構築をして無いねんから、そりゃ当然ですね。

とうことで、環境構築をすることにしましたが、ちょこちょこ詰まったので、ここに残しておきます。

主にこちらの記事を参考にさせていただきました。
https://qiita.com/standard-software/items/1afe7b64c4c644fdd9e4

とりあえずhttp-server コマンドのインストール

とにもかくにも、まずインストールを試みる

% npm install http-server -g

しかし、下記応答が。。。

zsh: command not found: npm

command not found: npmと出た時の対処

npmコマンドが使えないよ。とのことなので、以下の記事を参考にして進める。
https://qiita.com/sinmetal/items/154e81823f386279b33c

基本的には上記のリンク先通りにすればオッケー
ここでは、その過程で詰まったところを記載します。

source ~/.bash_profileコマンドで大量のエラー

.bash_profile が見当たらなかったので、下のサイトを見ながら、ホームに.bash_profile を作成。
http://banker0507.blogspot.com/2012/11/macbashprofile.html

https://qiita.com/sinmetal/items/154e81823f386279b33c
上記リンクに沿って下記コマンドを実行

% source ~/.bash_profile

すると下記のエラーが

command not found: rtf1ansiansicpg932cocoartf2513
command not found: cocoatextscaling0cocoaplatform0{fonttblf0fmodernfcharset0
command not found: colortbl
command not found: red255green255blue255
command not found: red255green255blue255
command not found: red83green83blue83
parse error near `;;'

なんだこれは?とりあえず.bash_profileの中身を見てみる

% cat .bash_profile

{\rtf1\ansi\ansicpg932\cocoartf2513
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fmodern\fcharset0 Courier;}
{\colortbl;\red255\green255\blue255;\red255\green255\blue255;\red83\green83\blue83;}
{\*\expandedcolortbl;;\cssrgb\c100000\c100000\c100000;\cssrgb\c40000\c40000\c40000;}
\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\deftab720
\pard\pardeftab720\sl380\partightenfactor0

\f0\fs26 \cf2 \cb3 \expnd0\expndtw0\kerning0

\outl0\strokewidth0 \strokec2 export PATH="/usr/local/bin:$PATH:/usr/local/sbin"\
export PATH=$HOME/.nodebrew/current/bin:$PATH
}

なぜかよくわからない文がたくさん、、、
viで中身を編集し、下記の状態にする。

% cat .bash_profile

export PATH="/usr/local/bin:$PATH:/usr/local/sbin"
export PATH=$HOME/.nodebrew/current/bin:$PATH

もう一度

% source ~/.bash_profile

無事エラーなしで解決

http-server実行でローカル環境での検証環境構築成功

その後もせこせこと参考ページを見ながらインストール等を実施し、最後に

% http-server

Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://192.168.10.7:8080
Hit CTRL-C to stop the server

http://localhost:8080/
にアクセスすると、、、、

無事、ローカルでの検証環境を構築することができました!

上記のサーバを停止したいときはcontrolキーとCを同時に押してください。

Node.jsで扱うよく扱うデータ形式の基礎知識

$
0
0

JSON

ご存知、JSON(JavaScript Object Notation)形式。
JSONオブジェクトは、標準ビルトインオブジェクトなので特別な準備なしで手軽に扱うことができる。

サンプル

JSONの例
{"browsers":{"firefox":{"name":"Firefox","pref_url":"about:config","releases":{"1":{"release_date":"2004-11-09","status":"retired","engine":"Gecko","engine_version":"1.7"}}}}}

使用方法

モジュールのインストールなどは不要。

// 文字列textをJSONとして解析し、JSオブジェクトに変換constobj=JSON.pares(text)// JSオブジェクトをJSON文字列に変換consttext=JSON.stringify(obj)

JSON5

JSON5は、JSONのスーパーセット。JSONの制限の一部を緩和する。

JSON5の拡張
* 末尾にコンマがあってもエラーにならない
* 文字列を一重引用符で囲むことができる
* 複数行の文字列を扱える(改行文字をエスケープする)
* 単一行および複数行のコメントが使える
* 空白文字を使用できる
など

サンプル

JSON5の例
{// commentsunquoted:'and you can quote me on that',singleQuotes:'I can use "double quotes" here',lineBreaks:"Look, Mom! \
No \\n's!",hexadecimal:0xdecaf,leadingDecimalPoint:.8675309,andTrailing:8675309.,positiveSign:+1,trailingComma:'in objects',andIn:['arrays',],"backwardsCompatible":"with JSON",}

インストール

Node.jsの場合
npm install json5
ブラウザの場合
<scriptsrc="https://unpkg.com/json5@^2.0.0/dist/index.min.js"></script>

使用方法

constJSON5=requrie('json5')// 文字列textをJSON5として解析し、JSオブジェクトに変換constobj=JSON5.pares(text)// JSオブジェクトをJSON5文字列に変換consttext=JSON5.stringify(obj)

CSON

CSON(CoffeeScript-Object-Notation)は、CoffeeScript記法を基にしたデータ形式。

サンプル

CSONの例
{"greatDocumentaries":["earthlings.com","forksoverknives.com","cowspiracy.com"],"importantFacts":{"emissions":"Livestock and their byproducts account for at least 32,000 million tons of carbon dioxide (CO2) per year, or 51% of all worldwide greenhouse gas emissions.\nGoodland, R Anhang, J. “Livestock and Climate Change: What if the key actors in climate change were pigs, chickens and cows?”\nWorldWatch, November/December 2009. Worldwatch Institute, Washington, DC, USA. Pp. 10–19.\nhttp://www.worldwatch.org/node/6294","landuse":"Livestock covers 45% of the earth’s total land.\nThornton, Phillip, Mario Herrero, and Polly Ericksen. “Livestock and Climate Change.” Livestock Exchange, no. 3 (2011).\nhttps://cgspace.cgiar.org/bitstream/handle/10568/10601/IssueBrief3.pdf","burger":"One hamburger requires 660 gallons of water to produce – the equivalent of 2 months’ worth of showers.\nCatanese, Christina. “Virtual Water, Real Impacts.” Greenversations: Official Blog of the U.S. EPA. 2012.\nhttp://blog.epa.gov/healthywaters/2012/03/virtual-water-real-impacts-world-water-day-2012/\n“50 Ways to Save Your River.” Friends of the River.\nhttp://www.friendsoftheriver.org/site/PageServer?pagename=50ways","milk":"1,000 gallons of water are required to produce 1 gallon of milk.\n“Water trivia facts.” United States Environmental Protection Agency.\nhttp://water.epa.gov/learn/kids/drinkingwater/water_trivia_facts.cfm#_edn11","more":"http://cowspiracy.com/facts"}}

インストール

npm install cson

使用方法

constCSON=require('cson')// 文字列textをCSONとして解析し、JSオブジェクトに変換constobj=CSON.pares(text)// JSオブジェクトをCSON文字列に変換consttext=CSON.stringify(obj)

HTML/XML/RSS

cheerioは、コアjQueryのサブセット。

インストール

npm install cheerio

使用方法

constcheerio=require('cheerio')const$=cheerio.load('<h2 class="title">Hello world</h2>')$('h2.title').text('Hello there!')$('h2').addClass('welcome')$.html()//=> <html><head></head><body><h2 class="title welcome">Hello there!</h2></body></html>

YAML

YAML(ヤムル、YAML Ain't a Markup Language)は、JSONのスーパーセットです。
設定ファイルによく使われています。

サンプル

リスト
---# お好みの映画、ブロック形式-Casablanca-Spellbound-Notorious---# 買い物リスト、インライン形式、またはフロー形式[milk,bread,eggs]
連想配列
---# ブロックname:John Smithage:33---# インライン{name:John Smith,age:33}

インストール

js-yamlのインストール
npm install js-yaml

使用方法

YAMLの使用方法
constyaml=require('js-yaml')constobj=yaml.safeLoad(txt)for(variinobj.items){constit=obj.items[i]console.log(it.name,it.price)}

INI

INI(イニ)ファイルは、昔から設定ファイルによく使われています。

サンプル

INIファイル
[section1]name1=value1name2=value2[section2]name3=value3

インストール

iniのインストール
npm install ini

使用方法

iniの使用方法
constini=require('ini')constobj=ini.parse(txt)for(constnameinobj){constit=obj[name]console.log(name,it.price,it.color)}

CSV

RFC4180準拠のcomma-separated-valuesを使う。

サンプル

"aaa","bbb","ccc"
zzz,yyy,xxx

インストール

node.jsの場合
npm install comma-separated-values
ブラウザの場合
<scriptsrc="csv.min.js"></script>

使用方法

constCSV=require('comma-separeted-values')constcsv=newCSV(txt,{header:fales)}constrecords=csv.parse()

Word/Excel/Powerpoint

officegenは、Microsoft Office 2007以降用のOffice Open XMLファイル(Word、Excel、Powerpoint)を、外部ツールなしで純粋なJavaScriptで作成します。

インストール

npm install officegen

使用方法

constofficegen=require('officegen')constfs=require('fs')// Create an empty Excel object:letxlsx=officegen('xlsx')// Officegen calling this function after finishing to generate the xlsx document:xlsx.on('finalize',function(written){console.log('Finish to create a Microsoft Excel document.')})// Officegen calling this function to report errors:xlsx.on('error',function(err){console.log(err)})letsheet=xlsx.makeNewSheet()sheet.name='Officegen Excel'// Add data using setCell:sheet.setCell('E7',42)sheet.setCell('I1',-3)sheet.setCell('I2',3.141592653589)sheet.setCell('G102','Hello World!')// The direct option - two-dimensional array:sheet.data[0]=[]sheet.data[0][0]=1sheet.data[1]=[]sheet.data[1][3]='some'sheet.data[1][4]='data'sheet.data[1][5]='goes'sheet.data[1][6]='here'sheet.data[2]=[]sheet.data[2][5]='more text'sheet.data[2][6]=900sheet.data[6]=[]sheet.data[6][2]=1972// Let's generate the Excel document into a file:letout=fs.createWriteStream('example.xlsx')out.on('error',function(err){console.log(err)})// Async call to generate the output file:xlsx.generate(out)

PDF

html-pdfは、開発終了のPhantomJSを使っているので使用は避けたいけれども。

インストール

html-pdfのインストール
npm install html-pdf

使用方法

html-pdf使用方法
arfs=require('fs');varpdf=require('html-pdf');varhtml=fs.readFileSync('./test/businesscard.html','utf8');varoptions={format:'Letter'};pdf.create(html,options).toFile('./businesscard.pdf',function(err,res){if(err)returnconsole.log(err);console.log(res);// { filename: '/app/businesscard.pdf' }});

【Node.js】JSON文字列をCSVファイルとして吐き出す

$
0
0

要件

・DBに格納されているデータをCSVでダウンロードできるようにする
・データはユーザーが回答したアンケートの結果

使用したモジュール

・json2csv
https://www.npmjs.com/package/json2csv

開発

1. データの成形

今回は、ユーザーが回答したアンケート結果をCSVとして出力したいと思いますので、表示する項目をユーザー名メールアドレスアンケート設問回答結果とします。

データを成形する
// DBから出力したいデータを取得するconstdata=awaitgetData.getUserData(user_id);// データをJSON型に整える constgroupData=data.reduce((result,current)=>{// user_idごとにデータをまとめるconstelement=result.find((p)=>{returnp.id===current.user_id;});// user_idが既にあればquestionとanswerのみを追加するif(element){element.data.push({question:current.question,answer:current.answer});}else{// user_idが切り替わった時に新規の要素としてgroupDataに追加するresult.push({id:current.user_id,name:current.user_name,email:current.user_email,data:[{question:current.question,answer:current.answer,}],});}returnresult;},[]);

DBから取得したデータをJSONに成形しました。reduce関数を使っているのは1クエリで適切な形でデータを取得できなかったためなので、必須ではありません。for文やmap関数を使ってきれいにしてください。
reduce関数でのデータをグループ化は以下を参考にさせて頂きました。


参考サイト
JavaScript オブジェクト配列をsqlのgroup byのように集計する


成形したデータは以下のようになりました。dataの個数は同じでなくても大丈夫です。

groupDataの中身
[{id:267,name:"taro",email:"taro@example.com",data:[{question:"好きなプログラミング言語は?",answer:["HTML","CSS","JavaScript"]},{question:"得意なプログラミング言語は?",answer:["HTML","CSS"]},{question:"苦手なプログラミング言語は?",answer:["JavaScript"]}]},{id:269,name:"jiro",email:"jiro@example.com",data:[{question:"好きなプログラミング言語は?",answer:["Go","JavaScript"]},{question:"得意なプログラミング言語は?",answer:["PHP","Ruby"]},{question:"苦手なプログラミング言語は?",answer:["Java"]}]},]

2. JSONをCSVに変換

続いてJSONに成形したデータをCSVに変換していきます。今回はjson2csvというモジュールを使用しました。他にもCSV系のモジュールはたくさんありましたので、自分にとって使いやすいものや仕様に沿っているものを選択すると良いかと思います。

get_csv.js
const{Parser,transforms:{unwind}}=require('json2csv');functiongetCsv(data,fields,pathes,res){consttransforms=[unwind({paths:pathes,blankOut:true,})];constjson2csvParser=newParser({fields,transforms});constcsv=data.length?json2csvParser.parse(data):'';res.setHeader('Content-disposition','attachment; filename=data.csv');res.setHeader('Content-Type','text/csv; charset=UTF-8');returnres.send(csv);}module.exports=getCsv;

引数のdataは先程成形したデータが渡ってきます。fieldsにはdataの中の表示させたい項目を配列で渡します。今回の例ですと、以下のようになります。

fieldsの中身
constfields=['name','email','data.question','data.answer'];

questionとanswerはdataの入れ子になっているのでドット繋ぎにします。

最後のpathesはdataの中でネストされている項目を配列で渡します。今回の例では以下のようになります。

pathesの中身
constpathes=['data']

ここで、pathesの中にdata.answerを入れると、answerに格納されている配列が展開されて表示されるようになります。今回は、一つの設問に対して複数の回答を1行で表示させたかったのでdataのみにしました。
他にも色々オプションがありますので、詳しくは公式ドキュメントを参照してください。

3. CSVの出力

後は、CSVに変換したデータをPOSTなどのルーティングでres.sendしてあげることで、ブラウザからファイルをダウンロードすることができます。

CSV出力
constcsv=getCsv(groupData,fields,pathes,res);res.send(csv);

出力結果

以上です。

YouTubeから動画をダウンロード(youtube-dl)

$
0
0

youtube-dlとは

youtube-dlは、YouTube.com やその他のサイトから動画をダウンロードするためのコマンドラインプログラムです。

Pythonインタプリタ、バージョン 2.6、 2.7、 3.2+ が必要で、プラットフォームに依存しません。UnixでもWindowsでもmacOSでも動作します。パブリックドメインに置かれているので、変更したり、再配布したり、好きなように使うことができます。

youtube-dlの使い方

コマンドラインで使います。

書式
youtube-dl [OPTIONS] YouTube動画のURL [URL...]
使用例
$ youtube-dl https://www.youtube.com/watch?v=7wfUUZvybPY
[youtube] 7wfUUZvybPY: Downloading webpage
[download] Destination: How to Install YouTube-DL (Windows 10)-7wfUUZvybPY.mp4
[download] 100% of 14.28MiB in 00:09

主なオプション

オプション説明
-u, --username USERNAMEYouTubeのアカウント
-p, --password PASSWORDアカウントのパスワード。省略すると対話的に入力
-o, --output TEMPLATE出力ファイル名のテンプレート
-s, --simulateシミュレートするだけで実際にはダウンロードをしない
-t, --title出力ファイル名を動画タイトルにする
-g, --get-urlダウンロードURLを表示する

アップデート

YouTubeに変更があるとyoutube-dlは使えなくなることがあります。その場合、アップデートする仕組みが用意されています。

youtube-dlの機能
sudo youtube-dl -U

上記でうまくいかない場合は、Windowsであればダウンロード、インストールのやり直し、Homebrew、pipは以下の方法でアップデートできます。

Homebrewの場合
brew update
brew upgrade youtube-dl
pipの場合
sudo pip install youtube_dl -U

インストール

Windows

Windowsユーザは、.exeファイルをダウンロードし、PATHを通して使ってください。

Mac

Homebrewでインストールすることができます。

brew install youtube-dl

もしくは、MacPortsでインストールすることができます。

sudo port install youtube-dl

pip

pipを使うこともできます。
https://pypi.org/project/youtube_dl/

sudo -H pip install --upgrade youtube_dl

Linux, MacOS, UNIX

curlを使う場合

sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
sudo chmod a+rx /usr/local/bin/youtube-dl

wgetを使う場合

sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
sudo chmod a+rx /usr/local/bin/youtube-dl

【Node.js】dotenvを使用して環境変数を設定&設定した環境変数をHerokuにも適用する方法

$
0
0

node.jsで環境変数を設定する場合、
「dotenv」を使用すれば楽に設定することができる
色々と遠回りというか勘違いをして時間がかかってしまったのでメモ

手順

  1. ルートディレクトリに「.env」という名前のファイルを作成
  2. ターミナルにて$npm install dotenv --saveを実行
  3. 1で作成した.envに環境変数として使用したいkey&valueを以下のように記載
.env
NODE_USER_ID=12345678NODE_PASSWORD=abcdefgh

4.環境変数を使用したい箇所に以下のように記載

app.js
require('dotenv').config();letuserId=process.env.NODE_USER_ID;// 12345678console.log(userId);

以上の手順で環境変数を設定できます。
※.gitignoreに .envを追記するのを忘れないようにしてください。
記載済みであれば大丈夫です。

あくまでもローカル上での設定となりますので、
Herokuにデプロイしているアプリに対して環境変数を適用する方法を以下に記載しました。

Herokuにデプロイする場合

便利ですよね、heroku。
Githubとherokuを連携している場合、.envファイルをプッシュしていないのでどうしたものかと・・・。

Config Varsを設定する

1.heroku上部メニューより、Settingsへ遷移
heroku_menu.png

2.「Reveal Config Vars」ボタンをクリック
reveal_config.png

3.お誂え向きなKEYとVALUEを入力できるフォームが出てくるので、
.envに記載している内容と同等のものを記載し、ADDをクリック
key_value.png

以上で、.envをgitに上げなくてもHerokuにデプロイしたアプリから、
環境変数を呼び出すことが可能となります。

備考

アプリ公開しました!よろしければインストールお願いします。
とらんぽ

Twitter始めました!よろしければフォローお願いします。
@yajima_tohshu


create-react-appを使用したReactの環境構築

$
0
0

はじめに

久々、環境構築をしたのでまとめました。
今回はcreate-react-appを使用してreactの環境構築をしていきます
どなたかの参考になれば。

環境

まずは作業を始める段階でのバージョンです。
homebrewは入っている前提で作業を進めていきます。

名前version補足
MacOS10.15Catalina
homebrew2.5
node.js12.9
yarn1.22stable
create-react-app3.4

以下、今回インストールする物のバージョンです。

名前version補足
nodebrew1.0
node.js14.10stable 更新します
yarn1.22stable
create-react-app3.4

nodebrewのインストール

まずhomebrewを使用して、nodebrewをインストールしていきます。
既にnode.jsが入っているので必要ないといえばないのですが、後々のことを考えてnodebrewを使用することにしました。
まずはインストール。

$ brew install nodebrew

私の場合、エラーが出ました。出なかった人はスルーしてください。
gccをインストールしないとビルド出来ないよとの事なのでインストールします。
終わったら再度nodebrewをインストールします。

Error: An exception occurred within a child process:
  CompilerSelectionError: nodebrew cannot be built with any available compilers.
Install GNU's GCC:
  brew install gcc

インストールが終了したら確認
バージョンが表示されたら成功です。

$ nodebrew --version

nodebrewがインストールされたらpathを通します。
私はCatalinaなのでzshですがbashの人は.bashrcで入力してください。
もちろんvimで直接入力しても問題ありません。

$ nodebrew setup

export...とパスが表示されるのでそれをコピーして↓のコマンドを入力
$ echo '{コピペしたやつ}' >> ~/.zshrc

次にnode.jsの安定版をインストールします。
バージョンを指定したい場合はstableの部分を指定のバージョンを書き入れましょう。
迷ったらとりあえず安定版。

$ nodebrew install-binary stable

インストールされていたら確認と指定をしていきます。

インストール出来ているか確認します
$ nodebrew list

今回はv14.10.1がインストールされていたのでこちらを使用します。
$ nodebrew use 14.10.1

nodeのバージョンを確認。14.10.1になっていればOKです
$ node -v

以上で、nodebrewとnode.jsのインストールは終了です。

react.jsの環境構築

yarnのインストール

node.jsのパッケージマネージャーであるyarnをインストールします。
こちらもhomebrewから。

$ brew install yarn

終わったら確認

$ yarn --version

react-create-app

reactはreact-create-appを使用するのが一番楽なのでyarnからこちらをインストールします。

$ yarn global add create-react-app

インストールされたら使用可能です!

$ create-react-app {名前}

まとめ

最近、自分のPCで開発をしていなかったので、なかなか長い道のりになりました。
作業を進めながら書いたので抜けはないかと思いますが、何かありましたらコメントお願いいたします!
近々react+typescriptの環境構築もするので、そちらもまとめますー!

Electronをバージョンアップしたらjsのネイティブモジュールが読み込めなくなった話

$
0
0

Electron9にバージョンアップしたところ、
途中でアプリの画面が動かなくなってしまう(windowになにも表示がされない)障害が発生しました。

原因

  • Electron9から、appallowrendererprocessreuseのパラメーターがデフォルトで true になってた
  • これによって2回目に読み込まれた時に、jsのネイティブモジュールが読み込めなくなっていた

appallowrendererprocessreuse とは

(公式サイトより) Boolean値。trueのときは、レンダラープロセスが確実に再起動されるように、Electronが設定しているオーバーライドを無効にする。このプロパティは、レンダラープロセスで使用できるネイティブモジュールに影響する
とのこと。名前からして再利用しようとしてうまくモジュールを読み込めてない、というような状況。

対策(暫定対応)

  • ページの初期化処理とかをしている部分で
    • app.allowRendererProcessReuse = false;
  • これまでと変わらないように動かせた

今後

  • issueによると

    • Electron 6 で app.allowRendererProcessReuse オプションを追加
    • コンテキスト非対応のネイティブモジュールについて、Electron 7で最初の非推奨警告ランドを用意する
    • Electron 9 でapp.allowRendererProcessReuseのデフォルト値がtrueに
    • Electron 10でapp.allowRendererProcessReuseを変更する機能を廃止予定
    • Electron 11でapp.allowRendererProcessReuseを変更する機能を削除
  • 今後Electron11では完全にfalseに変更できなくなるので、別の対応が必要

    • 現在調査中

【初心者向け】API利用の基本テクニック(ローディングの描画と標準時間の変換について)

$
0
0

背景とこの記事の目的

この度、TwitterAPIを利用したWEBアプリを構築しました。
その際に、「あーこの技術は今後も間違いなく活用するだろうなー」と思ったものがいくつかあったので、備忘として残します。
便利なライブラリもあったのでそちら紹介も兼ねます。
※フロントはvueで作っていますので、その前提で以下のトピックを紹介していきます。

目次

1.非同期通信の際のローディングでぐるぐるさせるやつ(スピナー、インジケーター)
2.APIのレスポンスの中にある時刻を日本時間に変換する

非同期通信の際のローディングでぐるぐるさせるやつ(スピナー、インジケーター)

image.png
こういうの実装したいと思ったことは皆さん誰しも一度はあるでしょう。
API通信で時間のかかる処理などでは非常に重宝します。

もし皆さんがvueを使っているのであれば、「vue-loading-overlay」をおすすめします。
詳しい使い方は以下が参考になります。
https://www.npmjs.com/package/vue-loading-overlay
https://www.kabanoki.net/4916/

カスタマイズもある程度できますし、比較的コードの記述量も少ないです。
axiosと組み合わせると以下のような書き方になります。

App.vue
//scriptタグ内の記述importLoadingfrom'vue-loading-overlay';import'vue-loading-overlay/dist/vue-loading.css';exportdefault{name:'app',data(){return{isLoading:false,fullPage:true,}},components:{"loading":Loading},methods:{getTimeline:function(){alert('Timelineを取得します');//overlayの処理letself=this;self.isLoading=true;axios.get('APIのURL',{headers:{//必要に応じて記載},params:{//必要に応じて記載}}).then(response=>{// handle successself.isLoading=false;}).catch(function(error){// handle errorself.isLoading=false;console.log(error);})},onCancel:function(){console.log('User cancelled the loader.')}//以下略
App.vue
<!-- templete内の記述 --><loading:active.sync="isLoading":can-cancel="true":on-cancel="onCancel":is-full-page="fullPage"></loading><buttonv-on:click="getTimeline"class= "btn btn-primary"> Timelineを取得 </button>

APIのレスポンスの中にある時刻を日本時間に変換する

TwitterのAPIのレスポンスの中に
"created_at": "Thu Apr 06 15:28:43 +0000 2017"
こういうものがありました。
APIのレスポンスには、必ずと言っていいほど時間に関する情報が入っています。そして日本のサービス出ない限り、大抵は標準時で入っています。
これを日本時間に変換する処理をする際は「Moment.js」というライブラリが便利です。

詳しい使い方は下記が参考になります。
https://qiita.com/osakanafish/items/5ef636bbcb2c3ef94953
https://momentjs.com/

実際の例としては以下の通り

App.vue
constmoment=require('moment');require('moment-timezone');//略(APIの処理).then(response=>{letobj=JSON.parse(response.data);moment.tz.setDefault('Asia/Tokyo');//日本時間に合わせるfor(vart=0;t<obj.length;t++){obj[t].created_at=moment(obj[t].created_at,'dd MMM DD HH:mm:ss ZZ YYYY','en').format('YYYY-MM-DD_HH:mm:ss');}

日付の記法は地域(というか国?)によってほんとに様々あるようで、それに応じてコーディングも微妙に変える必要があるようですね。

まとめ

実際に作ってみるといろいろと細かいテクニックが必要になることがよくわかりました。
ボタンの活性・非活性の制御、モーダルウィンドウ(子画面)の作り方、無限スクロールの実装の仕方などなど
上げればキリがないのですが、有用そうなものは時間があるときに記事にして残そうと思います

Node.jsのバージョンあげたら「Node Sass could not find a binding for your current environment」とでた場合

$
0
0

概要

Node.jsのメジャーバージョンをあげると、node-sassがエラーをはくことがある

「Node Sass could not find a binding for your current environment」

そのときは、node-sassをリビルドして自分のnode環境に合った状態にする

対処法

npm rebuild node-sass

これでOK

そのほか対処法

それでもだめなら

npm rebuild node-sass --force

それでもだめなら、いったんnode-sass消してから。いれなおす。
(入れ直しでも、環境にあった適切なbinaryが生成される)

npm uninstall node-saas
npm install node-sass --save-dev

Docker ComposeでNode.jsの環境構築

$
0
0

動機

Create React Appを少し遠回りしてはじめようをローカル環境を汚さずに実践したかっため、タイトルのような環境構築を目指しました。

最終的なディレクトリ構成

最終的なディレクトリ構成
node-docker/
    |--docker-compose.yml
    |--node/
        |--app/

docker-compose.yml

Docker Compose 概要
Compose とは、複数のコンテナを定義し実行する Docker アプリケーションのためのツールです。

まずプロジェクトフォルダnode-dockerを作成し、そこにdockercompose.ymlファイルを作成します。

現在のディレクトリ構成
node-docker/
    |--docker-compose.yml
docker-compose.yml
version:'3'services:node:image:node:14.9.0-alpine3.10container_name:nodevolumes:-./node/app:/apptty:trueports:-3000:3000
version

docker-compose.ymlのファイルフォーマットのバージョン

services

各コンテナをサービスとして定義できます。

node

サービス名

image

コンテナを実行時に元となるイメージを指定します。
イメージが存在していなければ、ComposeはDocker Hubからpull(取得)を試みます。
Dockerfileからイメージを作成する場合は、例えばnodeディレクトリの下にDockerfileを作成し、以下のように指定します。
build: ./node

container_name

デフォルトで生成される名前の代わりに、カスタム・コンテナ名を指定します。なお、デフォルトだとnode-docker_node_1という名前になります。

volumes

ローカルのパス(左)とコンテナのパス(右)を共有します。

tty

端末を起動するかどうかを指定しています。(多分)

ports

ポートを公開します。ホストとポートを指定(ホスト:コンテナ)するか、コンテナのポートのみ指定します(ホスト側のポートはランダムに選ばれます)。
exposeはポートを露出し、リンクされたサービス間でのみアクセス可能になります。

コンテナの作成と起動

ここまでで必要なファイルが揃ったので、コンテナの作成と起動を行っていきます。
Dockerfileからイメージを作成する場合は、Docker-compose buildする必要がありますが、今回は既にビルドされたイメージをリモートから取得するので必要ありません。
作業はdocker-compose.ymlが置いてあるディレクトリ上で行います。

terminal
$docker-compose up -d

dオプションにより、バックグラウンドで起動することができます。

nodeコンテナに入る

以上でnode環境は整いましたが、node環境はコンテナ上にあるため、作業はその上でする必要があります。
そのために、起動中のコンテナ内で指定したコマンドを実行できるexecコマンドを利用して、コンテナ上でシェル、端末を起動します。

terminal
$docker-compose exec node sh

コンテナ内のディレクトリ構成は以下のようになっていて、docker-compose.ymlで指定したためコンテナでのappディレクトリとローカルのnode/appディレクトリは対応しています。

コンテナ内のディレクトリ構成
/
    |--app
    |--bin
    |--dev
    |--home
    |--lib
    |--media
    |--mnt
    |--opt
    |--proc
    |--root
    |--run
    |--sbin
    |--srv
    |--sys
    |--tmp
    |--usr
    |--var

作業は、ローカルと共有できているappディレクトリでした方が良いと思います。

参考

Compose file version 3 reference
Compose ファイル・リファレンス
Docker Compose - docker-compose.yml リファレンス
【初心者向け】Dockerで手軽にNode.js開発環境構築 (2)

node.jsでサーバーを立ててみる

$
0
0

はじめに

私の最初の投稿になります。
qiitaについても素人同然なので何かあれば教えていただけると幸いです。

これから何回かに分けてチャットウェブアプリを作っていき、その過程をメモとして残していこうと思います。(mac 環境)
今回は node.jsを用いてサーバーを立ててみます。

Node.jsのインストール

まずnode.jsがインストールされているか確認をします。
node.jsのバージョンを確認します。
確認は

$node --versionv12.18.3

で確認。
node.jsが入っていない場合node.jsのインストールをおこなう。
node.jsのバージョン管理はnpm (Node Package Manager) か n を使って管理するとのことです。

今回nを使っていきたいのですが、nを入れるために。(ここら辺もいまいちわかっていないですが)
ともかく以下のコマンドで n をインストールします

$brew install node
$npm install-g n

-g オプションはグローバル環境で使えるようにするためにつけるようです。
n の簡単な使い方をまとめてみると

$n list         #インストール済みのバージョン一覧表示node/8.2.1
node/12.18.3
$n latest       #最新版をインストールする$n バージョン   #バージョンを指定してインストール$n            #何も指定しないとインストールされている中から選択する画面になる

といったところです。

node.jsのパッケージ初期化

まず以下のようにしてnode.jsのパッケージの初期化を行います。

$ npm init

これを行うと

package name: (training) 
version: (1.0.0)
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 

のよう入力を促してくるのでそれぞれ入力してエンターを押して進めていきます。
何も書かずにエンターすると()のなかのものがデフォルトで入力されます。
今回はentry pointのjsファイルの名前をserver.jsにして進めています。

最後yで確定するとpackage.jsonが作られます

pacage.json
{"name":"training","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC"}

node.jsのパッケージのインストール

今回使っていくパッケージをインストールしていく
今回はExpressとsocket.ioを使用していくためこの二つを以下のようにしてインストールする

$npm install express --save$npm install socket.io --save

--saveはpackage.jsonのdependenciesに書き込むオプションであるのでpackage.jsonは
余談ーーー
ちなみに--save--devとするとdevDependenciesに書き込まれるらしいです。
この違いはgit clone した時などに必要な環境を整える時にインストールするかどうかを分けているらしいです。
ーーーーー

package.json
{"name":"mychat","version":"1.0.0","description":"","main":"server.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC","dependencies":{"express":"^4.17.1","socket.io":"^2.3.0"}}

となる。
またこれによってnode_modulesディレクトリとpackage-lock.jsonファイルが作られる。

サーバーの起動

早速起動していきます。
サーバーの起動を行うファイルserver.jsを作ります(npm initでentry pointで指定した名前で作る)

'use strict';

// モジュール
const express = require( 'express' );
const http = require( 'http' );
const socketIO = require( 'socket.io' );

// オブジェクト
const app = express();
const server = http.Server( app );
const io = socketIO( server );

// 定数
const PORT = process.env.PORT || 1337;

// サーバーの起動
server.listen(
    PORT,
    () =>
    {
        console.log( 'Server on port %d', PORT );
    } );

これについて簡単に説明していきます。
まず

server.js
'use strict';

これは厳格(strict)モードとして実行することの宣言のようです。
つまりエラーなどの許容を厳格にして曖昧な表現を禁止したり動作の高速化、潜在的な問題をわかりやすくするなどあるようです。
次に

server.js
constexpress=require('express');consthttp=require('http');constsocketIO=require('socket.io');

で使われているrequier()
これはモジュールを読み込むために使用するようです。
余談ーーー
htmlでモジュールを読み込む時は<script>タグで行うが、それができないサーバーなどでモジュールを読み込むために作られた仕様のようです。
ーーーーー

server.js
constapp=express();constserver=http.Server(app);constio=socketIO(server);

1行目のexpress() はexpressモジュールの変数の初期化。
2行目の3行目はhttp.Server(app)はhttp.ServerインスタンスにexpressとsocketIOを両立させるために行なっているようです。(この辺りの根本はまだ理解に時間がかかりそうです)

server.js
constPORT=process.env.PORT||1337;

これはポート番号の指定している。
私がjs歴がほとんどないのでおそらくですがprocess.env.PORTは環境変数PORTを示していて、これをor演算を行なっていると考えると、もし環境変数PORTがfalseを示すものだった場合(None等)数値1337を変数PORTに代入しているのだと思います。
つまり環境変数として指定すればいつでもどのファイルとの統一を測れるのではないかと考えています。

server.js
server.listen(PORT,()=>{console.log('Server on port %d',PORT);});

server.listen(PORT、function)はサーバーを立ち上げる関数のようです(これは正しいかちょっと自信ないです。)
PORTはポート番号の指定をして、functionは立ち上げた時に働く関数を定義するようです。
今回関数はアロー関数式で定義しています。
(valiable)=>{function}
で記述できるようです。(アロー演算子はあまり使ったことがない)
ここの関数ではconsole.log()でコンソールに標準出力しているだけです。

余談ーーー
サーバーの起動はapp.listen(port)などいろんな方法があるようです。今回はexpressとsocket.ioを同じポートで待ち受けるためにこのような方法をとります。
ーーーーー

これでserver.jsの説明は以上です。
実際にサーバーを起動してみます。起動の仕方は

$node server.js
Server on port 1337

で行う。
止める時はCtrl+Cで止める。

公開フォルダ・ファイルの作成と指定

公開フォルダは実際に公開する公開ファイルを置いておくディレクトリのようです。

$mkdir public 

これは説明しなくてもいいかと思いますが、publicディレクトリを作っています。
そしてpublicディレクトリの中に実際に表示するindex.htmlファイルを以下のように記述します。

public/index.html
<!DOCTYPE html><htmlxmlns="http://www.w3.org/1999/xhtml"><head><metacharset="utf-8"/><title>My Chat</title></head><body><h1>My Chat</h1></body></html>

htmlについては別にまとめるつもりなのでそちらを参照
続いて公開フォルダを指定します。
server.jsのサーバーの起動の前に

server.js
app.use(express.static(__dirname+'/public'));

express.static関数に公開ディレクトリのパスを渡してあげて指定するようです。ここはまた今度調べます

サーバーにアクセス

実際にサーバーを起動してみてからブラウザの検索画面に"localhost:1337"を入力してアクセスする。
うまくindex.htmlが出てくれば成功です。

クライアントから接続要求と、接続時の処理の作成

この辺りの通信あたりは苦手意識があって忘れてるところも多いので今後勉強していきますので今回は簡単に流します……ごめんなさい
publicディレクトリのなかにクライアントから呼び出すjsファイルclient.jsを作る。

public/client.js
// クライアントからサーバーへの接続要求constsocket=io.connect();// 接続時の処理// ・サーバーとクライアントの接続が確立すると、//  サーバー側で、'connection'イベント//  クライアント側で、'connect'イベントが発生するsocket.on('connect',()=>{console.log('connect');});

呼び出された時に実行する関数をアロー関数で定義していると思います。
そしてhtmlファイルのbodyの末尾に以下を記述します

public/index.html
<script src="/socket.io/socket.io.js"></script><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><script src="client.js"></script>

そして最後にserver.jsの公開フォルダの指定の上に

server.js
// 接続時の処理// ・サーバーとクライアントの接続が確立すると、//  サーバー側で、'connection'イベント//  クライアント側で、'connect'イベントが発生するio.on('connection',(socket)=>{console.log('connection');});

を記述する。
これでもう一度サーバーを立ててブラウザから"localhost:1337"にアクセスすると、ブラウザの検証のコンソールとサーバーのコンソールにそれぞれ指定した標準出力が表示されれば成功となります。

server.jsを色々書き加えたのでここまでの更新状況を置いておきます。

server.js
'use strict';// モジュールconstexpress=require('express');consthttp=require('http');constsocketIO=require('socket.io');// オブジェクトconstapp=express();constserver=http.Server(app);constio=socketIO(server);// 定数constPORT=process.env.PORT||1337;// 接続時の処理// ・サーバーとクライアントの接続が確立すると、//  サーバー側で、'connection'イベント//  クライアント側で、'connect'イベントが発生するio.on('connection',(socket)=>{console.log('connection');});// 公開フォルダの指定app.use(express.static(__dirname+'/public'));// サーバーの起動server.listen(PORT,()=>{console.log('Server on port %d',PORT);});

終わりに

今回はチャットウェブアプリを作る前段階としてnode.jsでサーバーを立ててみました。
前にPHPを使ってサーバーを立ててみた時はapacheなど書くことが多く難しい、苦手、といった意識があったのですがnode.jsを使った時は意外と簡単に立てられたのが意外でした。
でもサーバー周りはなかなか理解が追いついてなかったり忘れてたりするのでこれを機に進めながら勉強し直していきたいと考えています。

[参考]

https://www.hiramine.com/programming/chat_nodejs_socketio/01_connect.html [クライアントとサーバーの接続時の処理を作る]
https://techacademy.jp/magazine/16151 [初期化処理を行う!npm initの使い方【初心者向け】]
https://qiita.com/heyheyww/items/092fcbc490a249a2d05c [npm install の --save-dev って何?]
https://qiita.com/havveFn/items/c5beda8572aa8c1e6be6 [npm install時に「--save」オプションはいらない]
https://qiita.com/miri4ech/items/ffcebaf593f5baa1c112 [【JavasScript】use strictとは]
https://qiita.com/uryyyyyyy/items/b10b012703b5396ded5a [require()とは何か?何が便利なのか]
https://teratail.com/questions/8894 node.jsでrequire('http');する理由
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14142502624知恵袋
https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback Node.js v14.10.1 Documentation
https://expressjs.com/ja/starter/static-files.html Express での静的ファイルの提供

NestJS+TypeORMでDB接続して動作確認まで

$
0
0

基本的に自分用メモです.

DBの準備

今回はmysqlを使います.

予め適当なデータベースとユーザを作成して権限付与までしておきます.テーブルはまだ作らなくてOKです.

CREATE DATABASE testdb;
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON testdb.* TO 'testuser'@'localhost';

NestJSのプロジェクト作成

NestJSのプロジェクトを作成して簡単な動作確認まで行います.

細かいところは公式ドキュメントを参照: https://docs.nestjs.com

npm i -g @nestjs/cli
nest new nest-typeorm-handson // とりあえずnpm選択して進めます
nest g module user
nest g controller user
nest g service user

これでnest-typeorm-handson以下にこんな感じにファイルが生成されます.

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
└── user
    ├── user.controller.spec.ts
    ├── user.controller.ts
    └── user.module.ts

最低限の動作の追加

UserServiceに以下を追加します

user.service.ts
sample():string{return'UserService';}

UserControllerに以下のコンストラクタとメソッドを追加します.

user.controller.ts
constructor(privatereadonlyservice:UserService){}@Get()get(){returnthis.service.sample();}

npm run startで起動し,ブラウザでlocalhost:3000/userにアクセスするとUserServiceの文字が出るはずです.

これで準備完了です.

TypeORMを使ってDB接続する

ここからTypeORMの出番です.

まずは準備します.

npm install --save @nestjs/typeorm typeorm mysql

そしてAppModuleの@Moduleの中身のimportsを以下のように書き換えます

app.module.ts
imports:[UserModule,// UserModuleはもともと追加されていますTypeOrmModule.forRoot({type:'mysql',host:'localhost',port:3306,username:'testuser',password:'password',database:'testdb',entities:[],synchronize:false,})],

さて,ここでnpm run startします.

すると以下のようなエラーが出る場合があります

[Nest] 70420   - 2020-09-11 22:23:00   [TypeOrmModule] Unable to connect to the database. Retrying (1)... +8ms
Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (/Users/home/nest-typeorm-handson/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)

これはユーザの設定関連が原因なのでmysqlで以下を実行すると治ります.

ALTER USER 'testuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

次にEntityを作ります.src/user/user.entity.tsを作成し,以下のように作ります.

user.entity.ts
import{Entity,PrimaryColumn,Column}from"typeorm";@Entity()exportclassUser{@PrimaryColumn({length:255})id:string;@Column({length:255})name:string;@Column()age:number;}

さらにAppModuleに先ほど書いたDBの接続情報にこのEntityを使うことを教えてあげます.

app.module.ts
TypeOrmModule.forRoot({// 一部省略database:'testdb',entities:[User],// <-追加})

UserModuleには以下を追加します.

user.module.ts
imports:[TypeOrmModule.forFeature([User])],

そして,Repositoryの準備です.UserServiceを以下のように書き換えます.コピペでOKです.

user.service.ts
@Injectable()exportclassUserService{constructor(@InjectRepository(User)privatereadonlyrepository:Repository<User>){}sample():Promise<Array<User>>{returnthis.repository.find();}}

さて,ここでnpm run startしてlocalhost:3000/userにアクセスするとエラーが出ます.

エラー内容はER_NO_SUCH_TABLE: Table 'testdb.user' doesn't existです.そりゃあそうですね,テーブルまだ作って無いですから.

マイグレーション機能でテーブルを作る

EntityなどからDDLを生成することができます.

そのために一旦先ほども書いたDBの接続情報を以下のように./ormconfig.tsにも書きます.

ormconfig.ts
module.exports={type:'mysql',host:'localhost',port:3306,username:'testuser',password:'password',database:'testdb',entities:['src/**/*.entity.ts'],migrations:['db/migrations/*.ts'],synchronize:false,cli:{migrationsDir:'db/migrations'}}

そしてpackage.jsonのscriptsに以下を追加します

"typeorm":"ts-node ./node_modules/.bin/typeorm --config ormconfig.ts"
npm run typeorm ---n Initialize

すると./db/migrationsに'数列-Initialize.ts'というファイルが生成され,中にはDDLが書かれていることがわかります.

そして,以下のコマンドを実行するとテーブルが作成されます

npm run typeorm migration:run
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| migrations       |
| user             |
+------------------+

そして再度npm run startすると起動できます

(もしかするとエラーが出るかもしれませんが,AppModuleのprovidersにUserServiceが指定されていたら消してください.自分の環境ではそれで治りました)

これで接続完了です.テーブルに適当にinsertしてからlocalhost:3000/userにアクセスするとinsertした内容が出力されるはずです.


かんたん Appleのヘルスデータをエクスポート、解析、csvに変換する方法

$
0
0

はじめに

みなさんは iOS アプリ Healthをつかってますか?
そんなみなさんは、体重や睡眠時間、歩数などの健康データはApple Healthのアプリ上に記録されていると思います。
このアプリは、健康状態の情報を記録し、アプリ上ではみえますが、実際には自分でデータ分析を行うことはできません。
データ分析とデータの可視化にはいろんな形式があると思いますが。今回はExcelやGoogle Sheetsのようなスプレッドシートアプリケーションを使って、解析するためにcsv 出力したいです。
今回、そんなみなさんにぴったりのCLIを作ったのでご紹介させていただきます.

Apple health dataの抽出方法

まずはこの動画を開いてみてください

  • iPhoneでHealthを開きます。
  • 上隅にあるプロフィールのアイコンをタップすしてください。
  • ヘルスのプロフィールの一番下までスクロールして、"Export All Health Data"をタップします。
  • 更に"Export"をタップすると、データをエクスポートすることを確認してエクスポート処理を開始します(完了するまでに少し時間がかかる場合があります)
  • 上記で抽出したファイルをローカルやGoogle Driveに保存してください

抽出したファイル

image.png
抽出したなら、その中の export.xmlを使います

Nodejs環境設定

本当の初心者のためのNode.js超入門 ~環境構築編~
などを参考にローカルにNodejsの環境を構築してください

コマンドインストール方法

下記のコマンドを叩いてahcdというコマンドをインストールしてください

$ npm i -g ahcd

使い方

$ ahcd                                                                                                                                                                                                
================================================================================
Apple Health Care Data convert xml to csv

Author     : Fumikazu Fujiwara 
Homepage   : https://github.com/freddiefujiwara/ahcd#readme
LICENSE    : MIT
Report bugs: https://github.com/freddiefujiwara/ahcd/issues
================================================================================

Usage: ahcd [-h]<file> [-t<type>][-d<dir>]
  • 引数の<file>は必ずexport.xmlを指定してください
  • -t は特定のcsvだけ出力します (例えば -t BodyMass など)
  • -d は出力先のディレクトリを指定します (例えば -d /path/to など)

実際に使ってみると

$ ahcd -d. export.xml                                                                                                                                                                                
Read export.xml
Analyze export.xml
Wrote ./Height.csv (1 records)
Wrote ./HeartRate.csv (87 records)
Wrote ./BodyMassIndex.csv (50 records)
Wrote ./BloodPressureDiastolic.csv (165 records)
Wrote ./BodyMass.csv (51 records)
Wrote ./BodyFatPercentage.csv (50 records)
Wrote ./FlightsClimbed.csv (1045 records)
Wrote ./BloodPressureSystolic.csv (165 records)
Wrote ./SleepAnalysis.csv (1193 records)
Wrote ./StepCount.csv (12032 records)
Wrote ./DistanceWalkingRunning.csv (13631 records)

最後に

さぁ ちょっと長かったですが、
これで、Excelなどのシートで解析できますね
またahcdpull request大歓迎です
よろしくおねがいします

Jestとpuppetterで繰り返しテストをarrayにまとめたサンプル

$
0
0

Jestとpuppeteerでe2eテストを書いています。大量のページに対してページのtitleをチェックしています。配列に対象ページのURLとtitleをまとめると、すっきり書けたのでメモしておきます。

配列

検査したい要素、titleとurlをまとめて指定しています。

constpages=[{'title':'はじめに - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',},{'title':'ダウンロード - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/download/',},{'title':'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/contents/',},];

テスト

testを配列のループで囲んでいます。ページを取得してtitleを照合しています。

for(constiinpages){consttitle=pages[i].title;consturl=pages[i].url;it('should be titled "'+title+'"',async()=>{awaitpage.goto(url);awaitexpect(page.title()).resolves.toMatch(title);});}

コード全体

loop.test.js
constpages=[{'title':'はじめに - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',},{'title':'ダウンロード - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/download/',},{'title':'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス','url':'https://getbootstrap.jp/docs/4.5/getting-started/contents/',},];describe('LOOP',()=>{beforeAll(async()=>{awaitpage.setDefaultNavigationTimeout(0);});for(constiinpages){consttitle=pages[i].title;consturl=pages[i].url;it('should be titled "'+title+'"',async()=>{awaitpage.goto(url);awaitexpect(page.title()).resolves.toMatch(title);});}});

実行結果

% jest loop.test.js
 PASS  ./loop.test.js
  LOOP
    ✓ should be titled "はじめに - Bootstrap 4.5 - 日本語リファレンス" (696 ms)
    ✓ should be titled "ダウンロード - Bootstrap 4.5 - 日本語リファレンス" (331 ms)
    ✓ should be titled "ファイル構成 - Bootstrap 4.5 - 日本語リファレンス" (515 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.251 s, estimated 9 s
Ran all test suites matching /loop.test.js/i.

気象庁アメダス観測データのAPI「JJWD」をきちんと作り直した

$
0
0

長らく停止しておりましたが2020年9月、再始動しました。
https://jjwd.info/
image.png

JJWD とは / 作り直した動機

JSONized Japanse Weather Dataの略で、気象庁が公開しているCSV形式のアメダス観測値データを使いやすいJSON形式に変換して提供するAPIサービスです。

当初は2017年のアドベントカレンダーに間に合わせるために構築したAPIでした。

停止したにもかかわらず、記事にコンスタントにLGTMがついておりました。
需要はあるものを放置したくはないですし、データを必要としている人も多いと思ったのできちんと使える形で整備した次第です。

image.png

使い方

使用に際して

気象業務法に抵触しない範囲で使用してください。
観測された値を基に何らかの処理を行って独自の予測を行う場合、資格ならびに気象庁の許可が必要です(観測されたままの値や、発表された気象予報を第三者に伝達することは問題ありません)。

また、気象庁以外が「警報」「注意報」などを発表することは許可されていません。

観測所検索

GET https://jjwd.info/api/v2/stations/search?

URL に以下のパラメータを付与して検索できます。

  • stn_name_ja: 観測所名 日本語(部分一致)
  • stn_name_en: 観測所名 英語(部分一致)
  • pref_ja: 都府県振興局 日本語(部分一致)
  • pref_en: 都府県振興局 英語(部分一致)
  • address: 住所 日本語(部分一致)

たとえば
https://jjwd.info/api/v2/stations/search?pref_ja=東京&address=世田谷
では、都府県振興局に 東京を含み、なおかつ住所に 世田谷を含む観測所が取得できます。

北海道は振興局単位なのでご注意ください。

/api/v2/stations/search?pref_ja=東京&address=世田谷
{"about":"JJWD - JSONized Japanese Weather Data:  https://jjwd.info/","datasource":"https://www.data.jma.go.jp/obd/stats/data/mdrr/","author":"Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .","version":"v2.0","stations":[{"stn_num":44126,"pref_ja":"東京","pref_en":"Tokyo","stn_type":"RobotRain","stn_name_ja":"世田谷","stn_name_ja_kana":"セタガヤ","stn_name_en":"SETAGAYA","stn_multipoint":false,"target_main_ja":null,"target_sub_ja":null,"target_main_en":null,"target_sub_en":null,"stn_temp":false,"stn_daylight":true,"address":"世田谷区岡本","address_sub":null,"lat":35.6267,"lng":139.62,"lat_sub":null,"lng_sub":null,"elevation":35,"alt_anemometer":null,"alt_thermometer":null,"elevation_sub":null,"alt_anemometer_sub":null,"alt_thermometer_sub":null,"start_date_rain":"1974-11-01","start_date_multi":null,"snow_stn_num":null,"updatedAt":"2020-09-11T19:00:32.933Z","preall":{"year":2020,"month":9,"day":12,"hour":19,"minute":50,"precip_1h_new_record_month":null,"precip_1h_new_record_in_decade":null,"precip_3h_new_record_month":null,"precip_3h_new_record_in_decade":null,"precip_6h_new_record_month":null,"precip_6h_new_record_in_decade":null,"precip_12h_new_record_month":null,"precip_12h_new_record_in_decade":null,"precip_24h_new_record_month":null,"precip_24h_new_record_in_decade":null,"precip_48h_new_record_month":null,"precip_48h_new_record_in_decade":null,"precip_72h_new_record_month":null,"precip_72h_new_record_in_decade":null,"precip_daily_new_record_month":null,"precip_daily_new_record_in_decade":null,"precip_1h":0.5,"precip_1h_q":8,"precip_1h_daily_max":13,"precip_1h_daily_max_q":5,"precip_3h":0.5,"precip_3h_q":8,"precip_3h_daily_max":24,"precip_3h_daily_max_q":4,"precip_6h":1,"precip_6h_q":8,"precip_6h_daily_max":24,"precip_6h_daily_max_q":4,"precip_12h":5,"precip_12h_q":8,"precip_12h_daily_max":26.5,"precip_12h_daily_max_q":4,"precip_24h":29,"precip_24h_q":8,"precip_24h_daily_max":29,"precip_24h_daily_max_q":4,"precip_48h":29,"precip_48h_q":8,"precip_48h_daily_max":29,"precip_48h_daily_max_q":4,"precip_72h":29.5,"precip_72h_q":8,"precip_72h_daily_max":29.5,"precip_72h_daily_max_q":4,"precip_daily":28.5,"precip_daily_q":4,"updatedAt":"2020-09-12T11:13:41.878Z"},"max_wind":null,"max_gust":null,"max_temp":null,"min_temp":null}]}

観測所番号を指定して取得

GET https://jjwd.info/api/v2/station/{stn_num}

観測所番号がわかっている場合はこちらを使用することができます。

/api/v2/station/46046
{"about":"JJWD - JSONized Japanese Weather Data:  https://jjwd.info/","datasource":"https://www.data.jma.go.jp/obd/stats/data/mdrr/","author":"Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .","version":"v2.0","station":{"stn_num":46046,"pref_ja":"神奈川","pref_en":"Kanagawa","stn_type":"RobotRain","stn_name_ja":"相模原中央","stn_name_ja_kana":"サガミハラチュウオウ","stn_name_en":"SAGAMIHARACHUO","stn_multipoint":false,"target_main_ja":null,"target_sub_ja":null,"target_main_en":null,"target_sub_en":null,"stn_temp":false,"stn_daylight":true,"address":"相模原市中央区中央","address_sub":null,"lat":35.5717,"lng":139.37,"lat_sub":null,"lng_sub":null,"elevation":149,"alt_anemometer":null,"alt_thermometer":null,"elevation_sub":null,"alt_anemometer_sub":null,"alt_thermometer_sub":null,"start_date_rain":"1975-05-16","start_date_multi":"1975-05-16","snow_stn_num":null,"createdAt":"2020-08-29T15:44:50.463Z","updatedAt":"2020-09-11T19:00:32.937Z","preall":{"year":2020,"month":9,"day":12,"hour":19,"minute":50,"precip_1h_new_record_month":null,"precip_1h_new_record_in_decade":null,"precip_3h_new_record_month":null,"precip_3h_new_record_in_decade":null,"precip_6h_new_record_month":null,"precip_6h_new_record_in_decade":null,"precip_12h_new_record_month":null,"precip_12h_new_record_in_decade":null,"precip_24h_new_record_month":null,"precip_24h_new_record_in_decade":null,"precip_48h_new_record_month":null,"precip_48h_new_record_in_decade":null,"precip_72h_new_record_month":null,"precip_72h_new_record_in_decade":null,"precip_daily_new_record_month":null,"precip_daily_new_record_in_decade":null,"precip_1h":0,"precip_1h_q":8,"precip_1h_daily_max":10,"precip_1h_daily_max_q":5,"precip_3h":0.5,"precip_3h_q":8,"precip_3h_daily_max":10.5,"precip_3h_daily_max_q":4,"precip_6h":2,"precip_6h_q":8,"precip_6h_daily_max":13.5,"precip_6h_daily_max_q":4,"precip_12h":5,"precip_12h_q":8,"precip_12h_daily_max":16,"precip_12h_daily_max_q":4,"precip_24h":18.5,"precip_24h_q":8,"precip_24h_daily_max":18.5,"precip_24h_daily_max_q":4,"precip_48h":18.5,"precip_48h_q":8,"precip_48h_daily_max":18.5,"precip_48h_daily_max_q":4,"precip_72h":19,"precip_72h_q":8,"precip_72h_daily_max":19,"precip_72h_daily_max_q":4,"precip_daily":17.5,"precip_daily_q":4,"updatedAt":"2020-09-12T11:13:41.884Z"},"max_wind":null,"max_gust":null,"max_temp":null,"min_temp":null}}

各パラメータの意味など、詳細はドキュメントをご参照ください
https://jjwd.info/doc-ja.html

データソース

基地局データ

気象庁の地域気象観測所一覧 [ZIP圧縮形式]から取得しています。
ZIP形式の中にCSVが格納されているのですが、開発中に提供されているファイルの名前が変わってしまい、取得できなくなるエラーが発生しました(気象庁さん、配布するファイルの名称は固定してほしい……)。

観測値データ

「最新の気象データ」CSVダウンロードから取得しています。データのつらさは相変わらずです。その辺は前回と同じようになんとかしました。

実装

Node.js + Express.js + Sequelize の無難な構成です。

ざっくりとしたディレクトリ構成
/
| - /schedule
| - | - fetch_amedas_stations.js
| - | - fetch_csv_files.js
| - /models
| - /public
| - | - /css
| - | - /js
| - app.js
| - package.json

データベース

Heroku Postgresを使用しています。件数的にはHobby devに収まるので無料の枠内で運用しています。

アクセス数が増えたら Connection Limit などを緩和するために Standard 0 などにアップグレードする必要がありそうです(Hobby Basicは行数の制限緩和のみなので)。

データの更新

Heroku SchedulerでNodeのスクリプトを走らせてUpsertしています。
Heroku Scheduler を使うと、Herokuにデプロイした repository 上にあるスクリプトを定時に走らせる事が可能です。DBのモデルやnpmパッケージも本体と共用できるので便利です。

観測値データの更新は 10 分おきに取得しています。
基地局データの更新は 1 日おきに取得しています。ただし、生の CSV データが配布されている観測値と異なり、基地局データは元データが zip で圧縮されているので /tmpにデータをダウンロードした上で展開する処理を挟んでいます(Heroku では /tmp/log以外にファイルを書き込めないので要注意です)。

静的ページ(ドキュメント)

シンプルに app.use(express.static(__dirname + '/public'));で配信しています。

デザインには Tailwind.cssを使用しています。
便利なCSSフレームワークですが、何もしないとファイルサイズが巨大なので、デプロイ前にpurgeやminifyしましょう。

また、軽くCSSアニメーションを入れたかったのでanimistaを使用しています。

デプロイ

Heroku を使用しています。
独自ドメインを使用したかったので、いまのところ月額7ドルの Hobby Dyno を使用しています。

独自ドメイン

公式ドキュメントを参考に、バリュードメインで以下のように CNAME で設定しています。

ドメイン設定
cname @ ****************.herokudns.com.

[メモ] TypeScript で hello-world!

$
0
0

<LABEL>-<MESSAGE><LABEL>-<MESSAGE><LABEL>-<MESSAGE>

はじめに

TypeScriptを仕事で使うことになったので最近勉強を始めました。
そのときのメモです。

TypeScriptをインストールする

公式サイトにインストールの手順が書いてあります。
自分の環境では以下のコマンドを使いました。
--save-devでTypeScriptコンパイラをローカル(プロジェクトフォルダ内)にインストールしています。

npm init -y
npm install typescript --save-dev

TypeScriptプロジェクトの設定 (tsconfig.json)

tsconfig.jsonという名前のファイルをプロジェクトのルートフォルダに作り、その中にコンパイラオプションなどの設定を書きます。
詳しくはこちらを参照。

tsconfig.json
{"compilerOptions":{"target":"es5","module":"commonjs","sourceMap":true}}

インテリセンスが効いているのでCtr+Spaceで入力候補を表示してその中から選ぶと良さそうです。
image.png

ビルドタスクを構成する

ターミナル(T) -> 既定のビルドタスクの構成...から tsc: ビルド tsconfig.jsonを選択します。
ビルドタスクの設定(tasks.json)は.vscodeフォルダ中にあります。

image.png

image.png

.vscode>tasks.json
{"version":"2.0.0","tasks":[{"type":"typescript","tsconfig":"tsconfig.json","problemMatcher":["$tsc"],"group":{"kind":"build","isDefault":true},"label":"tsc: ビルド - tsconfig.json"}]}

TypeScriptのコード

見ての通りです。
TypeScriptのことはまだよく分かっていないのでこれだけにしてきます。

main.ts
classMessage{constructor(publicmessage:string){}greet(){returnthis.message;}}console.log(newMessage("hello-world!").greet());

ビルド

Ctr+Shift+Bでビルドタスクを実行します。
プロジェクトフォルダ内のTypeScriptのコード(.ts)がJavaScriptのコードに変換され、デバッグ用のmapファイルが出力されます。

デバッグ

デバッグビュー > launch.jsonファイルを作成します。(リンク)からNode.jsを選ぶだけです。
他の言語の場合と大体同じ手順です。
デバッグ構成の詳細はこちらを参照。

image.png

.vscode>launch.json
{//IntelliSenseを使用して利用可能な属性を学べます。//既存の属性の説明をホバーして表示します。//詳細情報は次を確認してください:https://go.microsoft.com/fwlink/?linkid=830387"version":"0.2.0","configurations":[{"type":"node","request":"launch","name":"プログラムの起動","skipFiles":["<node_internals>/**"],"program":"${workspaceFolder}\\main.js"}]}

hello-worldを出力するファイルの名前をmain.tsにしたので"program"のファイル名をデフォルトの"index.js"から"main.js"に変更しました。

これでプログラムの起動、デバッグができます。
image.png

ウソ穴 Ver 6 の作り方

$
0
0

はじめに

個人開発ウソ穴の作り方を紹介します。

ウソ穴とは

ウソ穴は、ライブ映像 or 動画とARを組み合わせて、壁に穴が空いた錯覚を作り出します。Webサイトなので、ユーザーはアプリのインストール無くウソ穴を使用できます。

ウソ穴 Ver 6

今回は、Android端末でも動作実績のある Ver 6 を紹介します。

デモ映像

ウソ穴 Ver 6 Type B で顔に穴を開けてみました。

ウソ穴 Ver 6 Type A で壁に穴を開け、外のベランダの様子を見ました。

ウソ穴 Ver 6 Type A,B,C タイプ別の特徴

ウソ穴 Ver 6 は、TypeA,B,C の3種類あります。

  • ウソ穴 Type A / GStreamer
    • ライブ配信と組み合わせたウソ穴
    • ライブ配信はGStreamerを使用
    • 映像遅延 : ライブ配信の遅延に依存
  • ウソ穴 Type B / 時雨堂 sora
    • ライブ配信と組み合わせたウソ穴
    • ライブ配信は時雨堂さんの soraを使用
    • 映像遅延 : ほぼゼロ
  • ウソ穴 Type C / MP4動画
    • 動画と組み合わせたウソ穴
    • 映像遅延 : 動画ファイル読み込みの時間が必要

構成図

それぞれの構成図を紹介します。

ウソ穴 Ver 6 Type A / GStreamer

image.png

ウソ穴 Ver 6 Type B / 時雨堂 sora

image.png

ウソ穴 Ver 6 Type C / MP4動画

image.png

ソース

ソースは、githubに置きました。

作り方

作り方は、ウソ穴 Ver 5 と同じです。それぞれのリンクは以下です。

ウソ穴 Ver 6 Type A / GStreamer

作り方 -> ウソ穴 Ver 5 Type A / GStreamer

ウソ穴 Ver 6 Type B / 時雨堂 sora

作り方 -> ウソ穴 Ver 5 Type B / 時雨堂 Sora

ウソ穴 Ver 6 Type C / MP4動画

作り方 -> ウソ穴 Ver 5 Type C / MP4動画

line.png

ウソ穴の開発はつづく

Viewing all 9050 articles
Browse latest View live