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

node.js上のpuppeteerライブラリでHTML表の全行の要素をスクレイピングする

$
0
0

ターゲット

スクレイピング対象のターゲットHTMLは以下のようなWebページです。あるプロジェクトのメンバー一覧です。

スクリーンショット 2021-01-04 0.08.30.png

結論

HTML表の行数を求めるには、行を表すアイテムセレクター #content > center > table > tbody > trをブラウザの pageオブジェクトの $$()メソッドに指定します。そうしてメソッドの戻り値の lengthプロパティがHTML表の行数となります。

HTML表の行数を求める
letnumOfAccount=(awaitpage.$$('#content > center > table > tbody > tr')).length

求めた行数を変数 numOfAccountに格納して利用します。

HTMLの構造

一覧表はcenterタグ内にtableタグがあり、さらにtableタグ内にtbodyタグがあり、さらにtbodyタグ内にtrタグがあります。このtrタグ内の情報をスクレイピングします。

スクレイピング対象のターゲットHTML
<center><tableborder="1"bgcolor="white"rules="all"class="TableColor"><tbody><trbgcolor="#eeeeff"class="TableHeadingColor"><th>アカウント</th><th>氏名</th><th>メールアドレス</th><th>所属</th><th>ロール</th><th>アクセス表示</th><th>表示順</th><thcolspan="2">状態変更</th></tr><trbgcolor="#ffffff"><td><aname="9999999"><imgsrc="../images/user/invisible.png"></a><ahref="javascript:void(0)"class="userEditAnchor"data-account="9999999"data-role="2">9999999</a></td><td>帆花 風彦</td><td><ahref="mailto:hoge_fuuhiko@baaboo.co.jp">hoge_fuuhiko@baaboo.co.jp</a></td><td>馬場時時開発本部</td><td>開発メンバー</td><tdclass="textCenterTd"><inputtype="checkbox"class="showaccessmode"name="access_9999999"value="1"checked=""></td><td><spanid="oarea_9999999"><inputtype="hidden"name="order_9999999"value="1"id="order_9999999"><inputtype="button"class="disporderbtn"name="order_0272054"value="1"></span><inputtype="hidden"name="dborder_9999999"value="1"></td><td><inputtype="button"id="invalid_9999999"name="label"value="無効"></td><td>削除不可</td></tr>  ・・・・・・・・・・以下表の行数分<tr>タグを繰り返し・・・・・・・・・・
    </tbody></table></center>

アイテムセレクター

スクレイピング対象のターゲットHTMLの trタグのアイテムセレクターはタグの入れ子構造から

  • #content > center > table > tbody > tr

となります。

そこで、 TARGETオブジェクトのプロパティ

  • TARGET.project.userList.itemSelector.trTag'#content > center > table > tbody > tr'

と定義して利用することにします。

オブジェクト定義
constTARGET={projectList:[],url:{home:'https://target.hoge.jp/',project:{urlMask:'%ProjectNo%',alllist:'https://target.hoge.jp//manage/list.php',userList:'https://target.hoge.jp/users/list.php?project_id=%ProjectNo%'}},project:{allList:{rowNo:{top:2},colNo:{id:2,name:3,team:4},itemSelector:{rowNoMask:'%rowNo%',colNoMask:'%colNo%',trTag:'#content > form > center > table > tbody > tr',aTag:'> a',template:{row:'#content > form > center > table > tbody > tr:nth-child(%rowNo%)',col:'> td:nth-child(%colNo%)'}}},userList:{topRowNo:2,colNo:{account:1,name:2,mailAddress:3,belongs:4,role:5},itemSelector:{rowNoMask:'%rowNo%',colNoMask:'%colNo%',accountMask:'%account%',trTag:'#content > center > table > tbody > tr',aTag:'> a.userEditAnchor',invalidButton:'input[id=invalid_%account%]',searchButton:'searchbtn',template:{row:'#content > center > table > tbody > tr:nth-child(%rowNo%)',col:'> td:nth-child(%colNo%)'}}}}}

HTML表の行数の使い方例

page.$$( TARGET.project.userList.itemSelector.trTag ) ).lengthにより求められたHTML表の行数を格納した変数numOfAccountを最大値にしてforループで繰り返して、HTML表に格納されたデータをスクレイピングします。ここでは、アカウント氏名メールアドレスロールをスクレイピングしています。この処理を以下のgetAccountList()関数で実装します。

getAccountList()
constgetAccountList=asyncfunction(page,accountList){console.log('..... getAccountList() .....')let[account,name,mailAddress,belongs,role]=['','','','','']letaccountObj={}try{letnumOfAccount=(awaitpage.$$(TARGET.project.userList.itemSelector.trTag)).lengthconsole.log('<------ Project Member List Start ------>')for(letrowNo=TARGET.project.userList.topRowNo;rowNo<=numOfAccount;rowNo++){[account,name,mailAddress,belongs,role]=awaitgetAccountItem(page,rowNo)if(account=='')continueaccountObj={アカウント:account,氏名:name,メールアドレス:mailAddress,ロール:role}accountList.push(accountObj)}}catch(error){console.log('------> Project Member List End Over <------')}finally{if(Object.values(accountObj).length==0){accountObj={アカウント:'',氏名:'',メールアドレス:'',ロール:''}accountList.push(accountObj)}returnaccountList}}

【Node.js Express】Passport.jsを用いて認証機能を実装する。(passportLocalMongooseをプラグイン)

$
0
0

※当方駆け出しエンジニアのため、間違っていることも多々あると思いますので、ご了承ください。また、間違いに気付いた方はご一報いただけると幸いです。

要件

  • pasport.jsを用いたパスワード認証を作成する。
  • 認証ストラテジにはpassportLocalMongooseをプラグインで使用。
  • 入力する値はname email password
  • 認証(ログイン、サインアップ)以外のページはexpressデフォルトのホームページ("/")のみ。(そのページに認証をかける。)

アプリケーションに必要なパッケージをインストールする。

前提

・ express-generatorで雛形を作成済。
・ mongooseを用いてmongodbと接続完了。
・ package.json↓

package.json
{"name":"passport_app","version":"0.0.0","private":true,"scripts":{"start":"nodemon ./bin/www"},"dependencies":{"body-parser":"^1.19.0","cookie-parser":"~1.4.4",//依存"express-session":"^1.17.1",//依存"mongoose":"^5.11.9",//依存"passport":"^0.4.1",//本体"passport-local-mongoose":"^6.0.1", //本体"ejs":"^3.1.5","express":"~4.16.1","express-ejs-layouts":"^2.5.0",}}

前提を構築するまでの手順は、下記の記事を参考にしてください。
【アプリ開発 1】【Node.js express Docker】 Dockerを用いてNode.js Express MongoDB(mongoose)の環境を構築する【2020年12月】

一旦サインアップからユーザーを登録できるまで作成する。

とりあえずのビューを(共通、ログイン、サインアップ)作成する。

EJSレイアウトレンダリングをロードし、共通の土台レイアウトを作成する。

app.js
constlayouts=require("express-ejs-layouts");//モジュールロードapp.use(layouts);//ミドルウェアとして組み込む。

views/layout.ejs を作成する。
ヘッダーとメインコンテンツだけシンプルな構成

<!DOCTYPE html><html><head><metacharset="utf-8"><linkrel="stylesheet"href="/style.css"></head><body><header><p>名前:</p></header><divclass="main"><%-body%></div></body></html>

ログイン画面

image.png

views/users/login.ejs

<formaction="/users/login"method="POST"><label>Email</label><inputtype="text"name="email"autofocusrequired><label>Password</label><inputtype="password"name="password"required><buttontype="submit">Login</button></form>

サインアップ画面

image.png

view/users/signup.ejs

<formaction="/users/create"method="POST"><label>name</label><inputtype="text"name="name"autofocusrequired><label>Email</label><inputtype="text"name="email"autofocusrequired><label>Password</label><inputtype="password"name="password"required><buttontype="submit">Singup</button></form>

express-ejs-layoutsにより、ヘッダーを設定。後ほど名前には、nameを表示させる。

とりあえずのルーティング(ログイン、サインアップ)を作成する。

app.js
constusersRouter=require('./routes/users');//分離ファイルの読み込みapp.use('/users',usersRouter);// '/users'タッチ時のミドルウェアに分離ファイルを指定
routes/users.js
constexpress=require('express');constrouter=express.Router();constusersController=require('../controllers/usersController');router.get('/login',usersController.login);router.get('/new',usersController.new);router.post('/create',usersController.create);//routerに各メソッドと、遷移先を組み込み。module.exports=router;//routerオブジェクトをエクスポート

とりあえずのコントーラー(ログイン、サインアップ)を作成する。

controllers/usersController.js
constusersController={login:(req,res)=>{res.render("../views/users/login",{layout:false});//ログイン画面にはヘッダーは不要なので、layoutを表示させない様にしている。},new:(req,res)=>{res.render("../views/users/signup",{layout:false});//サインアップ画面にはヘッダーは不要なので、layoutを表示させない様にしている。},create:(req,res)=>{res.render("/");}}module.exports=usersController;

とりあえずのモデルを作成する。

m_user.js
"use strict";constmongoose=require("mongoose")constuserSchema=newmongoose.Schema({name:{type:String,required:true,//必須unique:true//ユニーク},email:{type:String,required:true,//必須unique:true//ユニーク},password:{//nameのバリューにオブジェクトで渡す。type:String,min:[4,"Too short"]//最小値エラーメッセージ を設定},});module.exports=mongoose.model("User",userSchema)

サインアップから保存処理。

usersController.js
constUser=require("../models/m_user");constusersController={//(略)new:(req,res)=>{res.render("../views/users/signup",{layout:false});},create:(req,res)=>{User.create({name:req.body.name,email:req.body.email,password:req.body.password},(error,result)=>{if(error){res.redirect("/users/new")}})}}module.exports=usersController;

ここまでの実装

サインアップ画面から、name、email、passwordをMongoDBに保存できる状態。

Passportを導入する。

app.jsで必要なミドルウェア設定を行う。

app.js
constpassport=require("passport")constcookieParser=require("cookie-parser");constexpressSession=require("express-session");//必要なパッケージをロードする。constUser=require("./models/m_user");//Userモデルをロードapp.use(cookieParser("secret"));//クッキーの秘密鍵の設定app.use(expressSession({secret:"secret",cookie:{maxAge:4000000},resave:false,saveUninitialized:false}));//セッションの設定app.use(passport.initialize());//passportを初期化ミドルウェアを組み込むapp.use(passport.session());//passportに対しセッションを使用するよう指定app.use(testUser.createStrategy());//デフォルトのログインストラテジーを設定する。app.serializeUser(User.serializeUser());app.deserializeUser(testUser.deserializeUser());//passportを、ユーザーデータの圧縮・暗号化/復号を行うように設定

Userモデルにpassport-local-mongooseプラグインを設定する。

m_user.js
"use strict";constmongoose=require("mongoose");constpassportLocalMongoose=require("passport-local-mongoose");//passport-local-mongooseをモジュールロードconstuserSchema=newmongoose.Schema({name:{type:String,required:true,//必須unique:true//ユニーク},email:{type:String,required:true,//必須unique:true//ユニーク},//passwordパラメータを削除(passportが自動的にハッシュとソルトのパラメーターを追加してくれる)});userSchema.plugin(passportLocalMongoose,{//userSchemaにpassportLocalMongooseをプラグインusernameField:"email"});//ログイン認証に使うパラメーターを指定(デフォルトのusernameから変更)module.exports=mongoose.model("User",userSchema)

コントローラーのcreateアクションにregisterメソッドを追記

usersController.js
constUser=require("../models/m_user");constusersController={login:(req,res)=>{res.render("../views/users/login",{layout:false});},new:(req,res)=>{res.render("../views/users/signup",{layout:false});},create:(req,res,next)=>{if(req.skip)next();constnewTestUser={name:req.body.name,email:req.body.email,};User.register(newTestUser,req.body.password,(error,user)=>{//registerメソッドはpassportLocalMongooseに組み込まれているメソッド。//第一引数に、認証以外に保存するパラメータ、第二引数に認証に用いるパラメータ(これがsaltとhash化される。)if(user){console.log("success")next();}else{console.log(error.message);next();}})}}module.exports=usersController;

サインアップから保存確認

image.png
パスワードの代わりに、saltとhashが保存されている。

ログイン時に認証を実装する。

コントローラーに、authenticateメソッドを追加する。

constpassport=require("passport");//passportのロードauthenticate:passport.authenticate("local",{//authenticateメソッドがstrategyから自動的に認証を確認。failureRedirect:"/users/login",//成功時の遷移先successRedirect:"/",//失敗時の遷移先}),

ルーティングのlogin入力ポスト時のルーティングを設定。

routes/users.js
router.post("/users/login",usersController.authenticate);

ユーザーの状態を保存するミドルウェアを組み込み。

app.js
app.use((req,res,next)=>{res.locals.loggedIn=req.isAuthenticated();//ユーザーのログイン状態を req.localsに格納res.locals.currentUser=req.user;//ログインユーザー情報を格納next();});

ログイン状態で名前を表示させる。(併せてログアウト遷移ボタンを追加)

views/layout.ejs
<header>
    <% if (loggedIn) { %>
      <p>名前:<%= currentUser.name %></p>
      <a href="/users/logout">Log out</a>
      <% } %>
  </header>

ルーティングにロウグアウトの遷移を追記

routes/users.js
router.get('/logout',usersController.logout);

ログアウト時の挙動をコントローラーに追記

controllers/usersController.js
logout:(req,res,next)=>{req.logout();res.redirect("/users/login");},

ログイン状態チェックのコントローラーを追記

controllers/usersController.js
loginCheck:(req,res,next)=>{if(!req.isAuthenticated()){res.redirect("/users/login");}next();}

ホームページに認証チェックとしてミドルウェアを埋め込む

routes/index.js
route.get('/',usersController.loginCheck,function(req,res,next){res.render('index',{title:'Express'});});

参考:

passport-local-mongoose - npm
Passport | 認証

部屋に置いてあるワインをそのまま飲んでよいか調べるために今の室温を測る。

$
0
0

ワインの飲み頃の温度に応じた色で光る

白に光った。
ピンクにも変わったりするので大体9℃前後です。
obniz.jpg

冬だから、部屋にお酒を置いておくだけでちょうどよい?

夏だったらキリっと冷やした白ワインを飲むときに冷えた状態に必要があって、いつも飲む直前にお店で買ってきていました。(そもそも保存の温度に気を使う必要があるので)

冬だったら室温でもちょうどよい温度になるのでは?と思い、
今の室温がどのワインの温度ぐらいかわかるようにLEDを光らせてみました。

ソースのなかに温度帯を細かく書いてますが、
大体でやってみてます。

ソース

constObniz=require('obniz');constobniz=newObniz('obniz_id');// Obniz_IDに自分のIDを入れてくださいobniz.onconnect=asyncfunction(){//温度センサーの設定consttempsens=obniz.wired('LM60',{gnd:9,output:10,vcc:11});varled=obniz.wired("WS2811",{gnd:0,vcc:1,din:2});//温度センサーの値が変わったら実行されるtempsens.onchange=function(temp){//スパークリングワイン       6~8度// 白ワイン(甘口)         6~8度// 白ワイン(辛口)         10度前後// 赤ワイン(ライトボディ)     12~14度// 赤ワイン(フルボディ)      16~20度if(temp>=6&&temp<9){led.rgb(255,0,255);// Pink}elseif(temp>=9&&temp<12){led.rgb(255,255,255);// White}elseif(temp>=12&&temp<20){led.rgb(255,0,0);// Red}elseif(temp<6){led.rgb(0,255,255);// Blue}else{led.rgb(0,0,0);// Black}console.log(temp);};}

Node.jsを勉強する③ - Javascriptファイル間の連携

$
0
0

はじめに

前回はテキストファイルの作成についてまとめました。
今回はJavascriptファイル間の連携について記事にします。

教材

Udemy
The Complete Node.js Developer Course (3rd Edition)
https://www.udemy.com/course/the-complete-nodejs-developer-course-2/

ファイルの作成

連携したいJavascrptのファイルを2つ作ります。
それぞれ、app.jsとutils.jsと名付け、utils.jsにconsol.logを用いてメッセージを書きます。

utils.js
console.log("The JS files are connected!")

ファイルの連携

今回は、utils.jsをapp.js上で呼び出します。
requireを用いますが、その際、呼び出したいファイル(今回はutils.js)までの相対パスで書きます。

app.js
require('./utils.js')

今回は、utils.jsとapp.jsを同じ階層に配置したので、"."を用いて階層をひとつ戻ってから、utils.jsと書きました。
ターミナルでnode app.jsを実行すると、"The JS files are connected!"と表示されます

ターミナル
node app.js
The JS files are connected!

他ファイルのオブジェクトを使う

今度は、他のファイルの中で定義されたオブジェクトを使います。
まずは、呼び出したい先のファイルに
module.exports = 取り出したいオブジェクト名 と書くことで、ファイル全体をエクスポートします。

utils.js
constname="Tom"module.exports=name

app.jsで呼び出し、console.logを用いて、出力してみます。
utils.jsはオブジェクトになっているので、変数を定義してapp.js上でも使えるようにします。

app.js
constutils=require('./utils.js')console.log(utils)

ターミナルで、app.jsを実行すると、utils.jsの変数nameの中身が取り出せます。

ターミナル
node app.js
Tom

複数のオブジェクトを取り出す場合

ひとつのjsファイルから複数のオブジェクトを取り出す場合は、配列にします。

app.js
constname="Taro"constaddress="Tokyo"module.exports={name:name,address:address}

取り出すときは、配列名を定義して、配列名.要素名で取り出します。

app.js
constutilsVariables=require('./utils.js')console.log("My name is "+utilsVariables.name)console.log("I live in "+utilsVariables.address)

実行結果は以下の通りです。

ターミナル
node app.js
My name is Taro
I live in Tokyo

Node.jsを勉強する④ - npmモジュールを使う

$
0
0

はじめに

前回はJavascriptファイル間の連携についてまとめました。
今回はnpmモジュールの使い方を記事にします

教材

Udemy
The Complete Node.js Developer Course (3rd Edition)
https://www.udemy.com/course/the-complete-nodejs-developer-course-2/

npmモジュールを開始する

ルートディレクトリでnpm initのコマンドを実行することでコンフィギュレーションファイルを作成し、npmモジュールの使用を開始する

ターミナル
npm init

するとpackage.jsonという名前のファイルが作られます。

package.json
{"name":"notes-app","version":"1.0.0","description":"","main":"app.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC"}

npmモジュールをダウンロードする

https://www.npmjs.com/にアクセスして、使用したいパッケージを探します。
今回はvalidatorを使います。validatorとサーチして、ページに飛んだら、右上のインストール用のコードをコピーします。
validator-npm.png

ルートディレクトリでこちらのコードを実行します。

ターミナル
npm i validator

すると、package-lock.jsonファイルが追加され、さらには、package.json内のdependenciesの中にvalidatorが表示されます。
これで、インストールが完了しました。

Validatorを使う

それでは、validatorを使ってみます。validatorを使いたいjsファイルに変数validatorを定義し、require('validator')を代入します。今回は、app.jsとファイルを名付けました。

app.js
constvalidator=require('validator')

こちらで、準備は完了です。実際に使ってみます。
仮にtest2021@example.comがEmailとしての要件を満たしているか調べて、結果を出力します。

app.js
constvalidator=require('validator')console.log(validator.isEmail('test2021@example.com'))
ターミナル
node app.js
true

Emailとしての要件を満たしているので、trueが出力されます。
その他のメソッドは、Validatorの使い方は、公式に記載がありますので、その都度参照します。

mongoDBにmongooseで接続できない対処

$
0
0

コマンドで$ node サーバーを起動するとサーバーは起動するがエラーが出る。以下
(node:2809) UnhandledPromiseRejectionWarning: Error: querySrv EREFUSED _mongodb._tcp.test.s1sce.mongodb.net
at QueryReqWrap.onresolve [as oncomplete] ...
その対処。

mongoose公式には
await mongoose.connect('mongodb://localhost/my_database', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
とあるが、これを
async () => {
await mongoose.connect('mongodb://localhost/my_database', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
}
を一部追加するとエラーは消えた。

Node.jsを勉強する - nodemonを使う

$
0
0

はじめに

nodemonの使い方を備忘録として、まとめておきます。

教材

Udemy
The Complete Node.js Developer Course (3rd Edition)
https://www.udemy.com/course/the-complete-nodejs-developer-course-2/

インストール

npmのウェブサイトのnodemonのページからインストールコマンドをコピーし、ターミナルで実行。

ターミナル
//ローカルインストールする場合
npm install nodemon
/グローバルインストールする場合
npm install -g nodemon

jsファイルの実行

node ファイル名の代わりに、nodemon ファイル名でファイルを実行します。
今回は、app.jsという名前のファイルを作り、実行します。

app.js
consol.log("Hello World!")
ターミナル
nodemon app.js

Hello World!
[nodemon] clean exit - waiting for changes before restart

この状態で、app.jsの変更を保存すると、自動でそのファイルが実行されます。

app.js
console.log(chalk.green.bold('Hello Node.js!'));

変更を保存すると、ターミナル上でapp.jsが実行され、以下のように表示されます。

ターミナル
nodemon app.js
Hello World!
[nodemon] clean exit - waiting for changes before restart

//ここで変更を保存
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
Hello Node.js!

nodemonの実行を止めるには、Ctrl + c をターミナル上で押します。すると通常の状態に戻ります。

node.jsにnodemonをインストールするが動かない人へ

$
0
0

node.jsにnodemonを入れると変更を監視してくれるので開発効率が上がる。
しかしnodemonをinstallしても動かない人一旦アンインストールして以下を試して欲しい。

エラー内容
bash: nodemon: command not found
解決方法
//アンインストールする
npm uninstall nodemon

//グローバルしてインストール
sudo npm install -g --force nodemon

ローカル開発環境にVSで開発するのが主流だと思うが、当方いつでもどこでも開発したいのでC9で開発しようと思ったらこのエラーにぶち当たった。
誰かの役に立てば。


Nodejs Eslintの初期化設定で失敗した時の対応方法

$
0
0

Nodejs Eslint 初期化設定で失敗した時の対応方法

npm install eslint@^x.xx --save-devコマンドを実施したあと、eslintをインストールし、
初期化を実施しようと、.\node_modules.bin\eslint --initコマンドを実施、
How would you like to configure ESLint? の質問に対して Answer questions about your styleを選択しEnterを押したところ、そこから何の進展もなかった時、その解決方法を共有します。

環境の整理

  • 開発環境:Windows10 Home 64ビットオペレーティングシステム、x64ベースプロセッサ
  • Node version: v12.xx.xx
  • ESLint version: v4.19.1

現象

not.png

解決方法

  1. Nodejsの公式サイト( https://nodejs.org/ja/)にアクセスし、node versionをダウンロード、アップデートする。
    ( 今回 Node version: v14.15.3 に更新 )

  2. 問題なく、進んでいることを確認する。
    yes.png

OK

【Node.js】開発に必要な基礎知識まとめ

$
0
0

はじめに

Node.jsで開発するにあたり必要な知識について手順とともにまとめてみました。

Node.jsについて

JavaScriptをサーバーサイドで動かす仕組みのこと

Expressについて

  • Node.jsでWebアプリの開発をするためのフレームワーク
  • Webアプリケーションを作る機能を提供する

Expressの導入方法

npmというシステムを使いインストールをする

$npm install express

インストールしたExpressの利用方法

jsへの記述

// expressの読み込みconstexpress=require('express');// expressを使用するための準備constapp=express();

サーバーの起動について

サーバーを起動し、Webアプリを画面に表示する方法です

jsへの記述

// アクセス可能なサーバーを起動する(ここではlocalhost:8080)app.listen(8080);

jsファイルを実行する

$node sample.js

ページ表示の仕組み

以下の流れでページの表示を行います
ブラウザからリクエスト送信 → サーバー(Node.js)→ HTMLをブラウザに返す

jsへの記述

// /topにリクエストが来たときにトップ画面を表示するapp.get('/top',(){// トップ画面を表示する処理を記述}

※URLに対応する処理を実行することをルーティングと言う

reqとresについて

  • ルーティングの処理ではreq(リクエスト)・res(レスポンス)の2つの引数を受け取る。
  • reqとresにはリクエスト・レスポンスに関する情報が入っている。

jsへの記述

// /topにリクエストが来たときにトップ画面を表示するapp.get('/top',(req,res)=>{// トップ画面を表示する処理を記述}

EJSについて

  • HTMLとjavaScriptのコード両方を記述できるNode.jsのパッケージ。

jsへの記述

// /topにリクエストが来たときにトップ画面を表示するapp.get('/top',(req,res)=>{// top.ejsを表示するres.render('top.ejs');}

EJSをインストールする

$npm install ejs

EJSを使用した値の表示

  • javaScriptのコードを記述するには<% %>または<%= %>で囲む。

    • <% %>の場合はブラウザに何も表示されないため、変数の定義に使用する。
    • <%= %>の場合はブラウザに表示される。変数の値を表示させたい時など。
  • res.renderと書くことで指定したビューファイルをブラウザに表示できる
    ※見た目を作るファイルをビューファイルと呼ぶ

EJSを使うメリット

  • 繰り返し記述しているコードをスッキリ書くことができる
  • forEachを利用するとスッキリ書くことができ、管理しやすくなる

EJSへの記述

<%constitems=[{id:1,name:'ねこ'},{id:2,name:'いぬ'},{id:3,name:'うさぎ'}];%><%items.forEach((item)=>{%><li><spanclass="id-column"><%=item.id%>   </span>
<spanclass="name-column"><%=item.name%></span>
</li>
<%});%>

CSSの適用方法

jsへの記述

// cssフォルダ内のファイルを読み込むapp.use(express.static('css'));

EJSへの記述

// 指定したcssファイルを読み込む
<linkrel = "stylesheet"href ="css/style.css">

【アプリ開発 2】【Node.js JavaScript MongoDB】 JavaScriptを用いて端末の現在地を取得する。【2021年1月】

$
0
0

Node.js Express MongoDBを用いたアプリ開発

Node.js Express MongoDBを用いたアプリ開発を行うことになりました。
開発の中で得られた知見を、支障のない範囲で記録していきたいと思います。

アプリの仕様上、各ユーザーの携帯電話の位置情報を取得し、googleマップ上で表示させる必要が生じました。
まずは、位置情報を取得する機能を実装してみます。

要件

  • ユーザーがログインし、「位置情報通知」ボタンをクリックした場合、位置情報がデーターベースに格納される。
  • ユーザーがログアウトもしくは、「位置情報通知解除」ボタンをクリックした場合、位置情報がデーターベースから削除される。
  • 24時になった時点で、ユーザーの操作なしに、「位置情報通知解除」状態となる。
  • ユーザーの位置が変更した場合、最新の位置情報がデータベースに格納される。
  • 管理者は、定期的にデーターベースにアクセスしGoogleマップを用いて、ユーザーの位置を確認することができる。
  • 位置情報には、ユーザーの連絡先と、コメント等が表示される。

Javascriptを用いて位置情報を取得するテスト

とりあえずブラウザにアクセスした段階で、位置情報が取得できるようにしてみます。

位置情報Geolocation API ついて

位置情報の取得についてh、Geolocation API を使用します。

Geolocation APIとは

ウェブアプリでユーザーの位置情報を取得したいと思うことはよくあります。例えば、ユーザーの位置を地図上にプロットしたり、ユーザーの位置に関連するパーソナライズされた情報を表示したりすることができます。

位置情報 APIは navigator.geolocation への呼び出しを介してアクセスします。これにより、ブラウザーはユーザーに自分の位置情報にアクセスする許可を要求します。ユーザーが許可すると、ブラウザーは端末上で利用可能な最良の機能を使用してこの情報にアクセスします (GPS など)。

https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API

<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initial-scale=1"><title>CAR MATCH APP</title></head><body><divid="position-display">取得中</div><script>// GPS センサの値が変化したら何らか実行する geolocation.watchPosition メソッドnavigator.geolocation.watchPosition((position)=>{letposition_y=position.coords.latitude;// 緯度を取得letposition_x=position.coords.longitude;// 経度を取得letaccuracy=position.coords.accuracy;// 緯度・経度の精度を取得displayData(position_y,position_x,accuracy);// displayData 関数を実行},(error)=>{// エラー処理(今回は特に何もしない)},{enableHighAccuracy:true// 高精度で測定するオプション});// データを表示する displayData 関数constdisplayData=(position_y,position_x,accuracy)=>{vartxt=document.getElementById("position-display");txt.innerHTML="緯度:"+position_y+"経度 "+position_x+"<br>"// データ表示+"精度: "+accuracy;}</script></body></html>

IMG_BBE4EF911ADD-1.jpeg

ページにアクセスするとブラウザ上にアクセスした端末の現在地が表示されます。

スマホからも確認できるか試してみます。

netlifyに簡易的にアップロードします。

https://app.netlify.com/drop

その際、ファイル名はindex.htmlにします。また、アップロードはディレクトリで行う必要があるので、適当に"test/index.html"のような構造にして、testディレクトリをアップロードします。
アップロード方法はディレクトリをドラックするだけです。

スマホからアクセスしたところ、同様に確認することができました。

node.jsから取得したデータをmongoDBに保存。

ユーザーがページにアクセスしたら、位置情報がデータベースに登録できる様にします。

5秒毎に、データを更新して、mongoDBに保存します。

<!DOCTYPEhtml><htmllang="ja"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initial-scale=1"><title>CARMATCHAPP</title>
</head>
<body><divid="position-display">取得中</div>
<script>constsendPosition=(latitude_i_y,longitude_k_x)=>{//URLパラメータで遷移。location.href=`/savePosition?latitude_i_y=${latitude_i_y}&longitude_k_x=${longitude_k_x}`//ページ遷移し、savePositionメソッドを呼び出す。}// GPS センサの値が変化したら何らか実行する geolocation.watchPosition メソッドnavigator.geolocation.watchPosition((position)=>{letlatitude_i_y=position.coords.latitude;// 緯度を取得letlongitude_k_x=position.coords.longitude;// 経度を取得letaccuracy=position.coords.accuracy;setTimeout(function(){//ページリロード後、5秒経過でsendPosition関数を呼び出し。sendPosition(latitude_i_y,longitude_k_x);},5000);},(error)=>{// エラー処理(今回は特に何もしない)},{enableHighAccuracy:true// 高精度で測定するオプション});</script>
</body>
</html>

c_positionコントローラー.savePositionアクションを呼び出し。

c_position.js
"use strict";constPosition=require("../models/m_position.js");constDriver=require("../models/m_driver.js");constgetPosition=(req,res,next)=>{res.render("v_getPosition");}constsavePosition=(req,res,next)=>{Driver.findOne({name:"driverTaro"}).populate("position").exec((error,data)=>{//ユーザーはテストで固定。本来はセッションから取得。該当するユーザーのpostionを更新する。Position.findOneAndUpdate({_id:data.position._id},{$set:{latitude_i_y:req.query.latitude_i_y,longitude_k_x:req.query.longitude_k_x}}).exec();})res.redirect("/getPosition");//再度位置情報取得へリダイレクト}module.exports={getPosition,savePosition};
{"_id":{"$oid":"5ff163eb514e011e79ccc284"},"latitude_i_y":●●●.574898349,"longitude_k_x":●●●.0487293840238428,"driver":{"$oid":"5ff163eb514e011e79ccc283"},"__v":0}

保存を確認。
これでユーザーが任意に位置情報をアプリに提供できるようになりました。

データーベースから値を取得し、位置を表示させます。

googlemapを利用するため、まずはgooglemaps apiキーを取得します。
https://developers.google.com/maps/documentation/android-sdk/get-api-key?hl=ja
apiキーの埋め込みはソースコードで見えてしまうのでセキュリティー対策も必要です。
参考

mongoDBからデータを取得して、地図上に表示。

管理者側がユーザーの位置情報を知るために、googlemap上に表示させる様に実装します。

まず、ユーザーが登録している位置情報を返却するapiを作成します。

constreturnCarPositions=(req,res,next)=>{Driver.find().populate('position').exec((error,data)=>{res.json(data);})}

ユーザー(Driver)モデルから全て取得し、位置情報をpopulateしjsonをレスポンスします。

続いて、管理者がアクセスするビューを作成します。

<head><title>CARMATCHAPP</title>
<style>/* Google マップを表示させるためには style 内で width と height の指定が必要 */#mapDiv{width:100%;height:1000px;}</style>
</head>
<body><divid="mapDiv"></div> <!-- 地図を置くdiv要素 -->
</body>
<script>constplaceCars=()=>{fetch('http://localhost:8080/admin/getCarPositions').then(response=>response.json())//fetchでapiを呼び出し。.then(data=>{data.map(d=>{// マーカーをつけるconstmarker=newgoogle.maps.Marker({position:{lat:d.position.latitude_i_y,lng:d.position.longitude_k_x},map:map});});})}functioninitMap(){navigator.geolocation.getCurrentPosition((position)=>{varlatitude_i_y=position.coords.latitude;// 緯度を取得varlongitude_k_x=position.coords.longitude;// 経度を取得varinitPos=newgoogle.maps.LatLng(latitude_i_y,longitude_k_x);// 初期位置を指定map=newgoogle.maps.Map(mapDiv,{// Map オブジェクトを作成して mapDiv に表示center:initPos,zoom:13,});placeCars();})}</script>
<scriptid="googleMapUrl"src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDl7XzTVOilq7dDEMXjWjJLXgjz6VCTymc&callback=initMap"></script>
動作確認

initMapは地図の初期設定を行う関数で、urlの末尾に"callback=initMap"という形で組み込まれているため、ページ読み込み時に呼び出されます。

initMap内でplaceCars関数を呼び出します。
placeCars関数で、先ほど作ったapiに対しfetchします。
返却されたデーターをmarkオブジェクトにし、initMapで作成したmapオブジェクトに埋め込み、マーカーを地図上に表示させます。

image.png

この様に複数の箇所にマーカーを表示させることができました。

今後の実装

現状は、ユーザーが任意のタイミングでブラウザを開き位置情報を送る必要があります。
これを、定期的に位置情報を送信するように実装したいと思います。
これには、service workerを用いて、サイトをPWA化する必要がありそうです。
もしくはネイティブアプリを作るか。。。

続く。

なお、ダミーデーターは東京付近の観光名所を指定してたんですが、どうしても表示させれずに途方にくれていました。もしやと思い地図をズームアウトしたところ

image.png

こんなところにいました。。。緯度と経度は、間違えないようにしようにしましょう。

参考:
Geolocation_API
こくぶん研究室
Node.jsとexpressを使って簡単なWebAPIを作成する
Google Maps JavaScript APIで複数マーカーをいい感じに表示する

GoogleAPIをNode.jsから叩いてみた

$
0
0

今更ながら、GoogleAPIをNode.jsから触ってみます。
Google Drive、Gmail、Google Calendar、Googleフォトなど、皆さんgoogleサービスにお世話になっているのではないでしょうか。
ちゃんと、APIも提供されていて、npmモジュールもあるので、今後もいろいろ使えるかもしれません。
本人認証して簡単なリスト表示をするところまでですが、後はリファレンスを見れば拡張できるかと思います。

GitHubに上げておきます。

poruruba/GoogleApiSample
 https://github.com/poruruba/GoogleApiSample

参考情報

googleapis/google-api-nodes-client
 https://github.com/googleapis/google-api-nodejs-client

Google Cloud Platform Console
 https://console.cloud.google.com/

Google CalendarのQuickStart
 https://developers.google.com/calendar/quickstart/nodejs

Google DriveのQuickStart
 https://developers.google.com/drive/api/v3/quickstart/nodejs

Google PhotoのQuickStart
 https://developers.google.com/photos/library/guides/get-started

GmailのQuickStart
 https://developers.google.com/gmail/api/quickstart/nodejs

Google SheetのQuickStart
 https://developers.google.com/sheets/api/quickstart/nodejs

GCPのプロジェクトの作成

まずは、Google Cloud Platformのプロジェクトを作成します。すでに作成済みの場合はそれを使ってもよいです。

 https://console.cloud.google.com/projectcreate

image.png

プロジェクト名は適当に「SampleProject」としておきます。
Googleアカウント認証は、GoogleのサーバとOAuth2プロトコルで認証しますので、OAuth2のクライアントIDを作成します。
左上のメニューのAPIとサービスの認証情報を選択します。

image.png

次に、上の方にある「+認証情報を作成」をクリックし、OAuthクライアントIDを選択します。
アプリケーションの種類には、「ウェブアプリケーション」を選択します。
名前は適当に「GoogleApiSample」とでもしておきます。

image.png

そして、承認済みのJavascript生成もとには、これから立ち上げるサーバのURLを指定します。
例えば、https://hogehoge:10443という感じです。
承認済みのリダイレクトURIには、これから立ち上げるWebページのURLを指定します。
こんな感じで作る予定です。https://hogehoge:10443/googleapisample/signin.html

作成できたら、クライアントIDとクライアントシークレットを覚えておきます。

利用するGoogleAPIを有効化

これから、先ほど作ったプロジェクトで、Google Drive、Gmail、Google Calendar、Google Photoを使えるように有効化します。
左上のメニューから、APIとサービス→ライブラリ を選択します。

image.png

検索入力のところに、driveと入力します。

image.png

そうすると、Google Drive APIが見つかりますので選択し、「有効にする」ボタンを押下します。

image.png

同様に、Gmail、Google Calendar、Google Photoも有効にします。

サーバを立ち上げる

とりあえず、GitHubにもろもろを上げておいたのでそれを展開します。

https://github.com/poruruba/GoogleApiSample

$ unzip GoogleApiSample-main.zip
$ cd GoogleApiSample-main
$ mkdir cert
$ npm install

HTTPSで立ち上げる必要があり、certフォルダにSSL証明書のファイルを配置します。
Let’s Encryptの場合は、cert.pem、chain.pem、privkey.pemです。

起動の前に、各環境に合わせて変更する必要があります。

「public/googleapisample/js/start.js」
以下の2か所を修正します。

/public/googleapisample/js/start.js
constGOOGLE_REDIRECT_URL='https://【サーバのホスト名】:10443/googleapisample/signin.html';constAUTHORISE_URL='https://【サーバのホスト名】:10443/gapi/authorize';

「api/controllers/gapi/index.js」
以下の3か所です。

/api/controllers/gapi/index.js
constGOOGLE_CLIENT_ID=process.env.GOOGLE_CLIENT_ID||'【クライアントID】';constGOOGLE_CLIENT_SECRET=process.env.GOOGLE_CLIENT_SECRET||'【クライアントシークレット】';constGOOGLE_REDIRECT_URL=process.env.GOOGLE_REDIRECT_URL||'https://【サーバのホスト名】:10443/googleapisample/signin.html';

起動は、以下です。

$ npm app.js

何も指定しなければ、HTTPSが10443ポートで立ち上がるかと思います。

さっそく動かしてみる

以下のURLをブラウザから開きます。

 https://【サーバのホスト名】:10443/googleapisample/index.html

サーバのホスト名は、GCPプロジェクトのOAuth2クライアントを作成したときのサーバのホスト名と同じである必要があります。

image.png

Authorizeボタンを押下します。

image.png

Googleアカウントのログイン選択画面が表示されます。
アカウントを選択すると以下の画面が表示されます。この画面は、アプリがテスト用であるために表示されています。(本番にしたい場合はhttps://support.google.com/cloud/answer/7454865)

image.png

詳細リンクをクリックし、【サーバのホスト名】(安全ではないページ)に移動 のリンクをクリックします。

image.png

これから触るGoogle Drive、Gmail、Google Calendar、Google Photoへのアクセス権を許可するかの確認が表示されます。許可ボタンを押下します。

そうすると、GoogleアカウントのもろもろのGoogle Drive、Gmail、Google Calendar、Google Photoの情報を取得したのち以下のように各テキストエリアに表示されて終了です。

image.png

流れを見てみる

まず、ログインから。Authorizeボタン押下から始まりました。
以下の部分が呼ばれます。子ウィンドウを作成し、/public/googleapisample/signin.htmlを開いています。Googleアカウントのログイン処理(の前半部分)は、signin.htmlに実装しています。

/public/googleapisample/js/start.js
do_authorize:function(){this.new_win=open(GOOGLE_REDIRECT_URL,null,'width=500,height=750');},

一方、signin.htmlでは、ページが表示されてすぐに、以下を呼び出しています。

/public/googleapisample/signin.html
window.location=window.opener.vue.make_authorize_url();

親ページのmake_authorize_url()を呼び出して戻り値のURLにページ遷移しています。

/public/googleapisample/js/start.js
make_authorize_url:function(){returnAUTHORISE_URL+'?state=abcd&prompt=true';}

Googleアカウントログイン用のURLを生成しています。URLはサーバ側で生成しています。

以下の部分です。
GOOGLE_SCOPEには、取得したい対象サービスをリストアップしています。(OAuth 2.0 Scopes for Google APIs)

/api/controllers/gapi/index.js
if(event.path=='/api/authorize'){varparams={scope:GOOGLE_SCOPE,access_type:'offline'};if(event.queryStringParameters.state)params.state=event.queryStringParameters.state;if(event.queryStringParameters.prompt)params.prompt='consent';constauth=newgoogle.auth.OAuth2(GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URL);varurl=auth.generateAuthUrl(params);returnnewRedirect(url);}else

実は、URLは固定なので毎回取得する必要はないのですが、今回は手順に沿って生成しています。
見ての通り、リダイレクトを返していまして、やっとGoogleアカウント認証ページに遷移します。

ログインが完了すると、またこのページ(/public/googleapisample/signin.html)に戻ってきます。その時に、codeがパラメータとして戻ってきますのでそれを処理します。

/public/googleapisample/siginin.html
if(searchs.code){try{if(window.opener)window.opener.vue.callback_authorization_code(null,searchs.code,searchs.state);}finally{window.close();}

取得されたcodeは認可コードと呼ばれており、以降で利用するアクセストークンの生成に必要なパラメータです。

この認可コードを親ページにcallback_authorization_code()関数を介して戻しています。

/public/googleapisample/js/start.js
callback_authorization_code:asyncfunction(err,code,state){if(err){alert(err);return;}this.progress_open();try{varparams={code:code};varjson=awaitdo_post("/gapi/token",params);console.log(json);

認可コードの処理は実はサーバ側で実施しますので、サーバ側に転送しています。

/api/controllers/gapi/index.js
if(event.path=='/gapi/token'){try{constauth=newgoogle.auth.OAuth2(GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URL);vartoken=awaitauth.getToken(body.code);console.log(token);auth.setCredentials(token.tokens);

これで、token.tokensにもろもろのトークンが取得できました。

後はこれを使って、Google Drive、Gmail、Google Calendar、Google Photoの情報を取得する処理をして、クライアントに返しています。

/api/controllers/gapi/index.js
constdrive=google.drive({version:'v3',auth:auth});vardrive_list=awaitdrive.files.list({pageSize:10,fields:'nextPageToken, files(id, name)',});varimage_list=awaitdo_get_image_list({pageSize:100},token.tokens.access_token);constcalendar=google.calendar({version:'v3',auth});varcalendar_list=awaitcalendar.events.list({calendarId:'primary',maxResults:10,singleEvents:true,orderBy:'updated'});constgmail=google.gmail({version:'v1',auth});varmail_list=awaitgmail.users.labels.list({userId:'me',});returnnewResponse({drive_list,image_list,calendar_list,mail_list});

クライアント側では受け取ったレスポンスをテキストエリアに表示しています。

/public/googleapisample/js/start.js
this.drive_list=JSON.stringify(json.drive_list.data.files,null,"\t");this.image_list=JSON.stringify(json.image_list.mediaItems,null,"\t");this.mail_list=JSON.stringify(json.mail_list.data.labels,null,"\t");this.calendar_list=JSON.stringify(json.calendar_list.data.items,null,"\t");

nodebrew による Node.js の利用

$
0
0

はじめに

Node.js 、および、バージョン管理システムとして nodebrew を使う場合を説明します。
これまで多くの人の解説と変わりありません。ここでは、わたしなりにまとめてみます。

利用のコンピュータ環境

$ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69
$

nodebrewのインストール前に

すでにインストール済みの node.js と npm をアンインストールする。

たとえば、インストーラーでインストールしていた場合は次の2つのコマンドでアンインストールできる。

$ lsbom -flspf /var/db/receipts/org.nodejs.*.pkg.bom \
| while read i;do
  sudo rm /${i}done$ sudo rm-rf\
/usr/local/lib/node \
/usr/local/lib/node_modules \
/var/db/receipts/org.nodejs.*

Homebrewのインストール

macOS(またはLinux)用パッケージマネージャー Homebrewを使えるようにする。

$ /bin/bash -c"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

通常は上述の1行コマンドで Homebrew を利用できるようになる。
私の場合、Homebrew を入れ直そうとしたとき、次のコマンドの実行を促されたので実行した。

$ git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow$ brew update

インストールできたか、以下のように確認できる。
もし、エラーメッセージが表示される場合は再インストール等を検討しましょう。

$ brew --version
Homebrew 2.7.1-125-gd820c9a
Homebrew/homebrew-core (git revision 8b711; last commit 2021-01-05)
Homebrew/homebrew-cask (git revision 5c3de; last commit 2021-01-04)$ brew
Example usage:
  brew search [TEXT|/REGEX/]
  brew info [FORMULA...]
  brew install FORMULA...
  brew update
  brew upgrade [FORMULA...]
  brew uninstall FORMULA...
  brew list [FORMULA...]

Troubleshooting:
  brew config
  brew doctor
  brew install--verbose--debug FORMULA

Contributing:
  brew create [URL [--no-fetch]]
  brew edit [FORMULA...]

Further help:
  brew commands
  brew help[COMMAND]
  man brew
  https://docs.brew.sh
$ 

nodebrew のインストール

homebrew によって nodebrew をインストールする。
次のコマンドを実行:

$ brew install nodebrew

インストール後、セットアップコマンドを実行する必要があります。

$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

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

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================
$

利用しているシェルに応じて、bashなら~/.bash_profile、zshなら~/.zprofileに次を追加:

export PATH=$PATH:$HOME/.nodebrew/current/bin

zshの場合、

$ echo'export PATH=$PATH:$HOME/.nodebrew/current/bin'>> ~/.zprofile

これを反映させる(zshの場合)

$ source ~/.zprofile

これで設定完了。

インストールできたら、nodebrew コマンドを実行してみます。

$ nodebrew

nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install<version>            Download and install<version> (from binary)
    nodebrew compile <version>            Download and install<version> (from source)
    nodebrew install-binary <version>     Alias of `install`(For backword compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for`list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias<key> <value>          Set alias
    nodebrew unalias<key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in<version> to current version
    nodebrew exec<version> --<command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4
$ 

現在の node.js インストール状態を見る。

$ nodebrew ls              
not installed

current: none
$ 

以上から node.js のインストールがまだされていない状態を示している。

Node.js のインストール

インストール可能なバージョン一覧を表示

$ nodebrew ls-remote
v0.0.1    v0.0.2    v0.0.3    v0.0.4    v0.0.5    v0.0.6    

・・・省略・・・

v14.14.0  v14.15.0  v14.15.1  v14.15.2  v14.15.3  v14.15.4  

v15.0.0   v15.0.1   v15.1.0   v15.2.0   v15.2.1   v15.3.0   v15.4.0   v15.5.0
v15.5.1   

・・・省略・・・

io@v3.0.0 io@v3.1.0 io@v3.2.0 io@v3.3.0 io@v3.3.1 
$

たとえば、現行のLTS版 v14.15.4をインストールする場合は次のとおり:

$ nodebrew install v14.15.4
Fetching: https://nodejs.org/dist/v14.15.4/node-v14.15.4-darwin-x64.tar.gz
################################################################################################################################################## 100.0%
Installed successfully
Mocha:~ akinori$ nodebrew ls
v14.15.4

current: none
$ node
-bash: node: command not found
$

current: noneなので、インストールしただけで、まだ使えない。

node.js のバージョン切り替え

インストールしたバージョンの node を使う

$ nodebrew use v14.15.4
use v14.15.4
$ nodebrew ls
v14.15.4

current: v14.15.4
$ node -v
v14.15.4
$ npm -v
6.14.10
$ 

node.js, npm のインストール完了。

installコマンドを使って、複数のバージョンをインストールし、useコマンドで今利用するバージョンを指定する、という使い方ができる。

node.jsの特定バージョンのアンインストール

v14.15.4 をアンインストールする。

$ nodebrew unsinstall v14.15.4

参考

nodebrew github
https://github.com/hokaccha/nodebrew

node.js のインストール・アップデート・バージョン切替えの手順(nodebrew、Mac)
https://qiita.com/oreo3@github/items/622fd6a09d5c1593fee4

【Mac版】node.jsのアンインストールと再インストール手順メモ
https://qiita.com/wagi0716/items/94193a80502f9d81a9e0

Homebrew
https://brew.sh/index_ja

楽天ブックスゲーム検索APIから取得した情報を元に愛らしい?キャラ達に記事を書かせてワードプレスに自動投稿する

$
0
0

楽天ブックスゲーム検索APIから取得した情報を元に愛らしい?キャラ達に記事を書かせてワードプレスに自動投稿する

はじめに

筆者はApple信者ならぬ

ニンテンドー信者です。
ニンテンドースイッチのソフトの発売予定は欠かさずチェックしています。
しかしながらゲームの発売日を忘れてしまうことが多々あります。

 
そこで『楽天ブックスゲーム検索API』を活用して、情報をすかさずチェックしていこうと思います。

 
しかし・・・
APIで情報を取得した後にどこかに情報をアウトプットしなければいけません。

 
そこで、以前お勉強用に構築して
すでに放置してしまっているワードプレスに情報をアウトプットしようと思います。
 
ただワードプレスにアウトプットするのであれば記事になっていないといけないので
今回はBOTに自動で記事を書かせて、ワードプレスに記事を自動で投稿していこうと思います。

 
 

前提

・収益を目的とはしていないので、そういった設計は皆無。
・ニンテンドーが好きだ!
・WPの勉強にもなるのでとりあえずやってみよう といったレベル。
・構築時間は2時間くらいです(記事の作成は3時間くらい)

 

必要なもの

構築済のワードプレス
https://ja.wordpress.org/

楽天ブックスゲーム検索API
https://webservice.rakuten.co.jp/api/booksgamesearch/

WP API
https://ja.wp-api.org/

BOT環境
node.js
※言語は何でも良い。RESTを叩きまくるだけなのでExcel VBAでもいいくらいだけど、日時で自動で動かしたいのでnodeにした。

 

楽天ブックスゲーム検索APIとは

楽天ブックスゲーム検索APIは、楽天ブックスで販売されているゲームの情報を取得することが可能なAPI。
APIのテストページが面白いよ!
https://webservice.rakuten.co.jp/explorer/api/BooksGame/Search/ 
 
 
 

WP APIとは

ワードプレスのAPIで『https://<ドメイン>/wp-json/wp/v2/』から始まるURLにRESTでリクエストを投げて
・記事を取得したり
・記事を投稿したりできる。

 
 
 

完成イメージ

完成品は以下のような感じです。
画伯なキャラ達がかけあいをしたあとにその日に発売するゲームを淡々と紹介していく
ただそれだけです。

キャラ達の会話パターンはランダムで生成していますが
パターンが少ないので徐々に増やしていく。

あとデザインが適当すぎるのでそのうち整える。

image.png

  
 

APIの準備

楽天APIの準備

楽天APIの準備はとても手軽です。
下記のURLからいきなり『作ろう』に行ってもいいんですが、せっかくなので『探そう』『試そう』をやってからが良いかと思います。

https://webservice.rakuten.co.jp/guide/#howToDevelop

 
楽天APIの準備はこれだけです。 

image.png

楽天APIの準備

楽天APIの準備はとても手軽です。
下記のURLからいきなり『作ろう』に行ってもいいんですが、せっかくなので『探そう』『試そう』をやってからが良いかと思います。

https://webservice.rakuten.co.jp/guide/#howToDevelop

 
『作ろう』から自分用のアプリIDを発行したら準備完了です。
楽天APIの準備はこれだけです。  リファレンスも豊富だし、実に簡単で親切です。

image.png

WP APIの準備

これがなかなかわかりづらいんですがググると

・APIの認証の認証のやり方がいくつかある
・プラグインが必要
・Apacheの設定変更が必要
・なんか色々必要

・・・と出てくるんですが個人利用なので、今回は前準備が少ないBasic認証で進めることにしました。
自分が行った手順は以下の通り

 

BOT用のユーザーを作成(権限は投稿者)

image.png

BOT用のユーザーの設定画面から新しいアプリケーションパスワード名を設定

image.png

 
 

パスワード名を作成するとパスワードが表示されるのでこれを控えます。

※このタイミングでしか控えることができません。 控え忘れたらパスワード名を消して作り直しましょう。
image.png

 
 

BOT用ユーザーのメールアドレスとパスワードをBase64変換します。

※このタイミングでしか控えることができません。 控え忘れたらパスワード名を消して作り直しましょう。

以下は例
 メールアドレスが以下だとする
 botbotbotbotbotbotbotbot@gmail.com

 パスワードが以下だとする
 bU1l Iv1r Hku9 CJfH f4Td 46I5

 
上記のメールアドレスとパスワードを例にBase64でキーを作成します。

 メールアドレスとパスワードをコロンでつなぎます
 botbotbotbotbotbotbotbot@gmail.com:bU1l Iv1r Hku9 CJfH f4Td 46I5

 これを以下のようにBase64化します
 image.png

 今回は @segur様の記事を参考にキーを作成してみました。
 https://qiita.com/segur/items/80a8f2e8642aedf83ae2

 これでWP APIにアクセスするためのキーは完成しました。

 
 

WP APIにアクセスできるようにワードプレスの設定変更します

設定からパーマリンク設定にいきます

image.png

 
共通設定が基本になっているとAPIに接続できないため、API以外に変更します

image.png

これでワードプレスのAPIの準備も完了です。

BOTの作成

全量を書いていくととても長くなるので要点だけ紹介します。

requireは『sync-request』『date-utils』

RESTをとにかく投げまくるので、『sync-request』でソースが散らからないようにします。
発売日がキーワードなので『date-utils』も重要です。

constrequest=require('sync-request');require('date-utils');

 
 

楽天APIでゲーム情報を取得する処理

楽天APIはめちゃくちゃ簡単です。

テスト画面でURLを自動生成できるのでそれをベースにします。
https://webservice.rakuten.co.jp/explorer/api/BooksGame/Search/

for(varpage=1;page<15;page++){if(!searchEnd){constURI='https://app.rakuten.co.jp/services/api/BooksGame/Search/20170404?format=json&hardware=Nintendo%20Switch&booksGenreId=006&sort=-releaseDate&applicationId=*******&page='+page;console.log(URI);varresponse=request('GET',URI);responseBody=JSON.parse(response.body);

今回使用したパラメータ
ページは1ページから15ページまで検索します。
image.png

JSONの中身もテストページから確認できます。
image.png

 

WPに情報をポストする処理

// WPに情報をポストする // dt 投稿日付// title ブログタイトル// content ブログの本文//functionwpPost(dt,title,content){varheaderJson;headerJson={"Content-Type":"Application/json","Authorization":"Basic Ym90Ym90Ym90Ym90Ym90Ym90Ym90Ym90QGdtYWlsLmNvbTpiVTFsIEl2MXIgSGt1
OSBDSmZIIGY0VGQgNDZJNSANCg0K"};// Authorization":"Basic のあとにBase64で作成したキーをくっつけるvarbodyJson;bodyJson={"date":dt,"title":title,"content":content,"status":"publish",// すぐに公開"categories":"14",// ブログのカテゴリ の環境だとカテゴリID14はニンテンドースイッチ"featured_media":getRimage()// ブログのイメージIDのURL 関数でいくつか用意したイメージIDをランダムで取得};varresponse=request('POST','https://********/wp-json/wp/v2/posts',{// ****は投稿先のWPのドメインheaders:headerJson,json:bodyJson});console.log(response);}

 
 

キャラ達の掛け合いを作成する処理の一例

// 吹き出し(右)作成functioncreateFukidashiR(serihu){varresult="";result=result+'<br>';result=result+'<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-10 sbs-flat sbp-r sbis-cb cf block-box"><div class="speech-person"><figure class="speech-icon"><img src="/wp-content/uploads/2020/12/neko1.png" alt="ビビニャン" class="speech-icon-image"/></figure><div class="speech-name">ビビニャン</div></div><div class="speech-balloon"><!-- wp:paragraph -->';result=result+'<p>'+serihu+'</p>';result=result+'<!-- /wp:paragraph --></div></div>';returnresult;}

 

 

ゲーム情報作成

// ゲーム情報作成functioncreateGameInfo(title,image,maker,price,info,url){varresult="";result=result+'<br><br>';result=result+'<table>';result=result+'<tbody>';result=result+'<tr><td colspan="2"><h2>'+title+'</h2></td></tr>';result=result+'<tr>';result=result+'<td rowspan="3"><img  src="'+image+'"></td>';result=result+'<td>'+maker+'</td>';result=result+'</tr>';result=result+'<tr><td><span style="font-weight: bold;color: #BF0000;">'+price.toLocaleString()+'</td></tr>';result=result+'<tr><td><textarea rows="10" cols="20" id="text" style="overflow-y:scroll;">'+info+'</textarea></td></tr>'result=result+'<tr><td colspan="2"><a href="'+url+'" target="_blank" ><img style="float: right; margin: 115.8em 151 151em 551em;" src="/wp-content/uploads/2021/01/rakuten.png"></a></td></tr>';result=result+'</tbody></table>';returnresult;}

 
 

ブログ記事の作成と投稿

一応過去の日付を指定して投稿もできるようにしてある。

// ブログ投稿functionstartPost(){// 自動で日付指定する場合//const dateWa = dt.toFormat("YYYY年MM月DD日");// 日付を指定する場合constdateWa='2020年12月24日'// WP用の日付に変換するvarwpDate=getWPDate(dateWa);// コンソール出力console.log('start!'+dateWa+'の情報');// ワードプレスに送信するコンテンツvarwpcontent="";// タイトル作成varblogTitle=createTitle(dateWa);// 見出し作成wpcontent+=createMidashi(dateWa);// 右にいる人のセリフwpcontent+=createFukidashiR(getRTsukamiSerihu(dateWa));// 左にいる人のセリフwpcontent+=createFukidashiL(getLkaeshiihu());// 右にいる人のセリフwpcontent+=createFukidashiR('おお! よろしく頼むよ~');// ゲーム情報を取得するconstrakuteGameInfo=getRakuteGameInfo(dateWa,wpDate);// WPに投稿するif(rakuteGameInfo!=undefined){// 投稿できる情報がある場合console.log('送信');wpcontent+=rakuteGameInfo;wpPost(wpDate,blogTitle,wpcontent);}else{// 投稿できる情報がない場合console.log('未送信');}}

課題

さっきも書いたけど
・いつかデザインを整える
・キャラ達の会話の種類とパターンを増やす
・会話をマルコフ連鎖で自動生成してみるのも面白いかも。

 
 

完成品

image.png

image.png

image.png

園バス待ち中、忘れ物今取りに帰っても大丈夫?が目に見える仕組み!

$
0
0

幼稚園に子供を通わせて9年目、3児のママです。
幼稚園バスのあるあるだと思うのですが

・忘れ物!まだ取りに帰っても間にあう?
・出かけにモタモタ。バス行っちゃった?
・急にトイレ!家に一度帰っても大丈夫かな?

などなど、幼稚園バスの居場所がすぐにわかれば良いのにな~と思ったことが何度もあります。

知りたい時にバスの居場所がわかると

・雨の日、寒い日、終わりが見えずに待ち続けるという事がなくなり
・忘れ物など、一度家に取りに帰る事が出来たら、園まで後から送っていかなくても済んだり

メリットは沢山あると思います。

そこで、幼稚園バスの居場所をアプリのインストール不要!LINEからバスの位置情報を受け取れるサービスの開発をしていきたいと思います。

必要な技術など

・バスの位置を取得するGPSデバイス(今回はiphone)
・LINEで位置情報を取得する為のLINE Messaging API技術
・開発言語にはnode.jsを利用

位置情報を取得する

MDN WEB Docsを参考に位置情報の取得を行いました。

index.htmlとmap.jsファイル2つのファイルを作りました。

<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initial-scale=1"><title>位置情報取得</title><script src="map.js"defer=""></script></head><body><buttonid="find-me">Show my location</button><br/><pid="status"></p><aid="map-link"target="_blank"></a></body></html>
functiongeoFindMe(){conststatus=document.querySelector('#status');constmapLink=document.querySelector('#map-link');mapLink.href='';mapLink.textContent='';functionsuccess(position){constlatitude=position.coords.latitude;constlongitude=position.coords.longitude;status.textContent='';mapLink.href=`https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`;mapLink.textContent=`Latitude: ${latitude}°, Longitude: ${longitude}°`;}functionerror(){status.textContent='Unable to retrieve your location';}if(!navigator.geolocation){status.textContent='Geolocation is not supported by your browser';}else{status.textContent='Locating…';navigator.geolocation.getCurrentPosition(success,error);}}document.querySelector('#find-me').addEventListener('click',geoFindMe);

webブラウザで実装し、【Show my location】をクリックすると位置情報が取得でき
image.png

取得した位置情報のリンクをクリックすると地図が表示されました。位置あってる。
image.png

次に、取得したデータをデータベースへ保存

Firebaseを利用して、データを保存します
Firebase CloudFirestoreの使い方を初心者が解説してみた
今回は、このような形でデータベースを作成しました。
image.png

作成したデータベースにsetします。
setはデータベースのドキュメントの中身を上書きします。
参考にしたサイト
Firebase Cloud Firestoreのデータ更新 setとupdateの違い

awaitdb.collection('map').doc('K9HEdPL7SQukhLGpIgLX').set({Latitude:position.coords.latitude,Longitude:position.coords.longitude});

特定のドキュメントに位置情報を更新する仕組みは構築できました。
次に、

Firebaseの情報をLINEで取得

LINEの位置情報APIを利用して位置情報を送ります。
LINE 位置情報メッセージ
リッチメニューを作成して、リッチメニューを押したら位置情報を送るという仕組みにしました。

returnclient.replyMessage(event.replyToken,{type:'location',title:'位置情報',address:'Aバス',latitude:text.Latitude,longitude:text.Longitude});

LINEの位置情報メッセージを利用すると、地図が出るのでとても分かりやすいですね。

image.png

最後に

デプロイ

位置情報取得ボタンのhtmlファイルは契約しているサーバーへアップロード
左端に位置情報のボタンがあるシンプルなものです。

image.png

LINE Messaging APIと連携をとっているnode.jsファイルはHerokuでデプロイしました。

実際に使ってみる

バスに乗って試す事は難しいので、今回は子供と使ってみました。
本来は15秒おきくらいに自動で位置情報を発信していきたいのですが、今回は手動でボタンを押したタイミングでの位置情報で実験をしていきます。

イマドコサーチで親から子供の居場所をいつもは調べるのですが、今回は特別に逆パターン。
意外と子供から聞かれる
「ママいつ帰ってくるの?」

これをLINEで解決してもらいます!

①まずはママがスマホを持ってスーパーへ出かける
②子供は好きなタイミングで【今どこ?】を押す
③帰宅後、LINEを見せてもらう

結果

「ママずっと同じ場所にいた!」

子どものLINEを確認すると、全て同じ地図の場所にピンがありました。
念のため、薬局で位置情報を確認したのですが、薬局に位置が変わっている~!と私は感動しました。

考察

実験をしてみて、子どものLINEでは居場所が変わらなかった

この件に関して、
Firebaseを確認したところ、最後にwebアプリ側から位置情報取得ボタンを押した緯度にちゃんとなっていました。
LINEを立ち上げっぱなしにしていた事により、最新のデータが取得できなかったのではないかな?と思います。

ボタンを押したタイミングではなく、リアルタイムで最新情報を取得する
今度の課題たと思いました。


Node.jsを勉強する⑤ - JSONファイルの書き込みと読み込み

$
0
0

はじめに

前回は、 npmモジュールの使い方についてまとめました。
今回は、JSONファイルの作成と読み込みを記事にします。

教材

Udemy
The Complete Node.js Developer Course (3rd Edition)
https://www.udemy.com/course/the-complete-nodejs-developer-course-2/

データをJSONファイルに書き込む

まずは、app.jsファイルを作成して、Javascriptのオブジェクトを作成します。変数personを定義し、代入しておきましょう。

app.js
constperson={name:"Taro",age:25}

次に、このデータをJSONファイルに変換してみます。
変換には、JSON.stringify()というメソッドを使います。データは変数のpersonJSONに代入しておきます。

app.js
constperson={name:"Taro",age:25}constpersonJSON=JSON.stringify(person)

データがJSON化されたので、これをファイルに書き出します。ファイルの操作を行うには、fsモジュールを使います。requireを用いて、fsモジュールを使えるようにします。

また。ファイルを作成するには、writeFileSync(ファイル名, データ)を使います。今回は、info.jsonという名前のファイルを作成します。

app.js
constfs=require('fs')constperson={name:"Taro",age:25}constpersonJSON=JSON.stringify(person)fs.writeFileSync('info.json',personJSON)

以下のようなJSONファイルが作成されたら、完成です。

info.json
{"name":"Taro",age:25}

JSONファイルを読み込む

今度は作成したJSONファイルを読み込んで、consoleに出力してみます。
新しくread.jsを作って試してみます。readFileSync(ファイル名)を用いて、データを取り出します

read.js
constfs=require('fs')//データを取りだすconstbufferData=fs.readFileSync('info.json')

ただし、この状態ではデータはバイト列なので、文字に変換する必要があります。

read.js
constfs=require('fs')//データを取りだすconstbufferData=fs.readFileSync('info.json')// データを文字列に変換constdataJSON=bufferData.toString()

最後に、データをJavascriptのオブジェクトに変換する処理をしてから、consoleに出力します。

read.js
constfs=require('fs')//データを取りだすconstbufferData=fs.readFileSync('info.json')// データを文字列に変換constdataJSON=bufferData.toString()//JSONのデータをJavascriptのオブジェクトにconstdata=JSON.parse(dataJSON)console.log(data)

コードを実行し、ターミナルに、以下のように出力されれば、完成です。

ターミナル
node read.js

{ name: 'Taro', age: 25 }

また、データをJavascriptのオブジェクトに変換しているので、以下のように要素を取り出すこともできます。

read.js
constfs=require('fs')//データを取りだすconstbufferData=fs.readFileSync('info.json')// データを文字列に変換constdataJSON=bufferData.toString()//JSONのデータをJavascriptのオブジェクトにconstdata=JSON.parse(dataJSON)constname=data.nameconstage=data.ageconsole.log(data.name+' is '+data.age+' years old.')
ターミナル
node read.js

Taro is 25 years old.

Markdown正規表現全文検索ツール「Note-CLI」をリリースしました。

$
0
0

Markdown正規表現全文検索ツール「Note-CLI」をリリースしました。

以下かんたんな日本語版クイックスタートになります。

Markdown Indexing and Pcre Regular Expression Compatible Full Text Searching for Advanced Note Takers.

ノートテイキング上級者のためのMarkdownインデックスとPCRE正規表現対応の全文検索システム。

クイックスタート

# データベースへMarkdownファイルをインデックス indexing file
./note-cli.js --index--database notes.db.example --file notes.md.example

# データベースで正規表現でキーワード検索 searching for keywords by regular expression
./note-cli.js --search--database notes.db.example --header"python"--content"hello.?world"--hide-sql# 出力結果 and you will get:[{"id": 2,
        "header": "how to hello world in python",
        "content": "```py \n# how to hello world in python\n\nprint(\"hello world\")\n```\n\n","entire_note": "# how to hello world in python\n\n```py \n# how to hello world in python\n\nprint(\"hello world\")\n```\n\n"}]

コンセプト Basic concept

There are great note taking tools over there, such as Evernote. But the reason I quit using Evernote was its poor searching capability (i.e. cannot search some special characters; cannot search with regular expression, of course) and also, lack of extensibility. That's where Note-CLI comes in.

I'm kind of person who likes to take a note on my favorite text editor (Atom editor, for my case). For note taking, I have a single Markdown format text file. In it, I do take notes about everything. Note-CLI does work for indexing chunks of Markdown content to a database file and enables you to search your Markdown notes with SQL query (and also regex).

巷には既に便利なノートテイキングツールが数多くあります。例えば僕はEvernoteの愛用者でしたが、Evernoteでは検索において特定の特殊文字を使えない、正規表現が使えない、など、欠点があります。オープンソースではないので、拡張性の低さもその一つです。そこにNote-CLIの活用場面があります。

僕は大抵のノートテイキングは、お気に入りのAtomエディタから、一つのMarkdown形式のファイル上で行います。そこにほぼ全ての知識があるわけです。Note-CLIはこのようなMarkdown形式で記述されたノートをパーシングしデータベースへインデックス、SQLおよび正規表現でのノートの検索を可能にします。

インストール Installation

git clone https://github.com/yuis-ice/note-cli.git
cd note-cli
chmod 755 ./note-cli.js # if needed
npm i

必要環境 Requirement

  • node.js v13.10.1 (probably higher or other versions work)
  • sqlite3-pcre
# node.js [nvm-sh/nvm](https://github.com/nvm-sh/nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
bash
nvm install v13.10.1
node -v# sqlite3-pcresudo apt update
sudo apt install sqlite3 sqlite3-pcre

例 Examples

https://yuis.xsrv.jp/images/ss/ShareX_ScreenShot_da0baae1-0746-49b4-ab0d-790b1adddb7c.png

./note-cli.js --search--database notes.db.example --header"python"--content"hello.?world"--limit 1 --hide-sql--format md | bat -l md --paging=never # using [sharkdp/bat](https://github.com/sharkdp/bat) *image
./note-cli.js --search--database notes.db.example --header"python"--content"hello.?world"--limit 1 --hide-sql--format json | jq ".[].header"-r# using [jq](https://github.com/stedolan/jq/)
./note-cli.js --search--database notes.db.example --header"python"--content"hello.?world"--limit 1 --hide-sql--format html | jq ".[].entire_note"-r
./note-cli.js --search--database notes.db.example --header"python"--content"hello.?world"--limit 1 --raw-sql"$(cat raw-sql.sql.example )"

コマンドラインオプション Command line options

$ ./note-cli.js --help
Usage: note-cli [options]

Options:
  -i, --index            set command type: index
  -f, --file <file>      (index) specify file to index (default: null)
  -s, --search           set command type: search
  -r, --regex            (search) enable regex extension for search (default: true)
  --header <keyword>     (search) search by header (default: ".")
  --content <keyword>    (search) search by content (default: ".")
  --note <keyword>       (search) search by note (default: ".")
  --limit <number>       (search) set limit for search (default: -1)
  -S, --raw-sql <sql>    (search) use raw SQL query for search (default: null)
  -H, --hide-sql         (search) disable showing sql query executed
  -F, --format <format>  (search) output format (default: "json")
  --pcre-path <file>     set sqlite3 pcre file path for search (default: "/usr/lib/sqlite3/pcre.so")
  -d, --database <file>  specify database file for index/search (default: "./note-cli.db")
  --delete-database      delete database
  -y, --yes              no confirmation prompt
  -h, --help             display help for command

3分でできる Node.js + TypeScript + Jest プロジェクトの雛形作成

$
0
0

 こんにちは、mballです。
 つい最近転職をしまして、がっつりTypeScriptを触るようになったので学習も兼ねて、備忘録を残したいと思います。
 今回はタイトルの通り、一からNode.js + TypeScript + Jest のプロジェクトを作成する際の手順をご紹介します!

初期設定

まずは package.jsonを作成します。

$ mkdir node-typescript-test/
$ cd node-typescript-test/
node-typescript-test$ npm init -y
node-typescript-test$ ls
package.json

次に、 Node.jsとTypeScriptに関わる必要なモジュールをインストールします。

node-typescript-test$ npm install typescript --save-dev
node-typescript-test$ npm install @types/node --save-dev
node-typescript-test$ npx tsc --init--rootDir src --outDir lib --esModuleInterop--resolveJsonModule--lib es6,dom --module commonjs
node-typescript-test$ npm install ts-node --save-dev

そして、テストに必要なJestに関わるモジュールのインストールをします。

node-typescript-test$ npm i jest @types/jest ts-jest -D

npm installnpm i--save-dev-Dは同義です。TypeScriptのドキュメントに沿って、コマンド実行しています。

テストの設定

まずは、設定ファイルを作成します。

node-typescript-test$ touch jest.config.js

作成した jest.config.jsに設定を記述します。

jest.config.js
module.exports={"roots":["<rootDir>/src"],"testMatch":["**/__tests__/**/*.+(ts|tsx|js)","**/?(*.)+(spec|test).+(ts|tsx|js)"],"transform":{"^.+\\.(ts|tsx)$":"ts-jest"},}

テストを実行する

テストを実行しやすいように package.jsonの記述を変更します。

package.json
{"name":"node-typescript-test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"jest"//ここを修正},"keywords":[],"author":"","license":"ISC","devDependencies":{"@types/node":"^14.14.20","ts-node":"^9.1.1","typescript":"^4.1.3"}}

次にテストに必要なフォルダとファイルを作成します。

node-typescript-test$ mkdir src/
node-typescript-test$ touch src/foo.ts
node-typescript-test$ mkdir src/__tests__
node-typescript-test$ touch src/__tests__/foo.test.ts

jest.config.jsrootDirsrcディレクトリに設定しているため、テストする元ファイルとテストのファイルは全て srcディレクトリ配下に格納します。
また、 testMatchで指定しているようにテストのファイルは __tests__ディレクトリ配下に格納してあげると良いです。
最後にテストが通ることを確認するため、作成したファイルに以下のコードをそれぞれ記述します。

src/foo.ts
exportconstsum=(...a:number[])=>a.reduce((acc,val)=>acc+val,0);
src/__tests__/foo.test.ts
import{sum}from'../foo';test('basic',()=>{expect(sum()).toBe(0);});test('basic again',()=>{expect(sum(1,2)).toBe(3);});

テストを実行してみます。

node-typescript-test$ npm test

すると以下のように結果が表れ、無事テストが成功しました。

スクリーンショット 2021-01-06 22.29.25.png

ドキュメントは正義

今回の実装をするにあたり、一次情報だけを参考にしました。原典至上主義に一歩近づけた気がします。

参考

Node.js & TypeScriptのプロジェクト作成
Jest

EC2(AmazonLinux2)でnode.jsを起動するまでの過程記録(nodemonを使う)

$
0
0

前提として行ったこと

①AWSへの登録
②EC2サーバーの新規立ち上げ
③RDBの新規構築
④ローカル環境で制作したフォルダの、EC2へのアップロード

今回はnodemon を使いました。
これによってシステムの内容に変更があった時、再起動しなくても適応される(はず)。

以下、EC2サーバー上で正しく動作させるために行ったことを記録する。

目次

行ったこと
→パーミッション設定
→「npm start で起動するようにする」
→必要なもののインストール
→DB情報の書き換え
→エラー「Permission denied」
→nodemonのインストール
おまけ(永久実行)
参考文献

行ったこと(失敗も含む)

パーミッション設定

編集したいファイルは、フォルダごと権限を777にした。
$sudo chmod 777 フォルダ名orファイル名
権限を変えるのは危ないので「ローカルで書き換えてアップロード」を繰りかえす方がいいかも。
(今回は個人製作でいじられても重大事件になるようなシステムではないので気軽に変えた)

「npm start」で起動するようにする

package.jsonに以下を追加

"scripts":{"start":"nodemon ./bin/www"}

これは絶対に必要ではない。
直接nodemon ./bon/wwwと打ち込んでも実行できる。

必要なもののインストール

npm install
$curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo bash -
$sudo yum -y install npm

node.js install
$sudo yum -y install nodejs

nodemon install
$sudo npm install -g nodemon

postgresql install
ここを見ました

express install
$npm -g install express

DB情報の書き換え

ローカル環境で開発した際、index.jsのソースコードの中に書いた接続情報は、ローカル環境でしか動作しない。
EC2サーバー上で正常に動作させるために、

host: 'localhost' → host: '[AWSで作成したDBのエンドポイント]' ←変更
port: 5432 ←追加

と書き換えた。

ここで
$npm start (もしくは$nodemon ./bin/www)
を実行したが、Permission denied (権限エラー)で実行できず。

エラー「Permission denied」

/node_modules/.bin/nodemon: Permission denied
npm ERR! code ELIFECYCLE
npm ERR! errno 126
npm ERR! ths-web-page@1.0.0 start: `nodemon ./bin/www`
npm ERR! Exit status 126
npm ERR!
npm ERR! Failed at the ths-web-page@1.0.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

wwwがあるフォルダまでのすべてのフォルダの権限をゆるくしてみる
権限エラーが出ていたので権限制限ゆるくしてみた。
※当たり前ですが会社のシステムとかで勝手に権限ゆるくするのは絶対にダメ!

→まだ同じエラーが出ます。多分この操作は意味がなかった

nodemonをインストールする

$sudo npm install -g nodemon
このコマンドを打ったらインストールされ、
正常に$npm start (もしくは$nodemon ./bin/www)を実行できた!!

それにしてもなぜ権限エラーに??

おまけ(永久実行)

node.jsを、AmazonLinuxからログアウトした後もそのまま起動しておくには、
$sudo npm install -g forever
でインストールして
$sudo forever start ./bin/www
というコマンドを叩く。

参考文献URL

Amazon EC2でnode.js,Expressアプリケーションを立ち上げる
Amazon Linux 2 に PostgreSQL 11 をインストールする
npm start の使い方
node.js バージョンアップコマンド
AWS EC2 AmazonLinux2 Node.jsをインストールしてnpmコマンドを使用できる様にする

「エンジニアに向いている人」はGitHub user内にどれぐらいいるのか

$
0
0

注意

  • この記事はとあるツイートを話題にしていますが、筆者にツイート内容、およびツイートされた方を蔑む意図は一切ありません。
  • 筆者は統計、およびプログラミングに関してはまだまだひよっこです。したがって誤った見解や拙いコードを書いている可能性がありますが、お気づきの際は是非コメントにてご指摘ください。

背景

あけましておめでとうございます!
正月休みもあけ仕事も始まりましたが、みなさまいかがお過ごしでしょうか。

さて、正月早々、とあるツイートが反響を呼びました。

プログラミングスクール通ってるかどうかとかどうでもよくて、この年末年始にコード全く書いていない人はエンジニア向いてないんじゃないですかね、それぐらい好奇心が必要な職業だと思うけど

賛否両論あったこのツイートですが、内容はさておき、私はあることが気にかかりました。

「果たしてこの年末年始、コードを書いた人はどのぐらいいるのだろうか」

本記事では上記の疑問を検証していこうと思います。

レギュレーション

もちろんすべてのエンジニアに「この年末年始、コード書きましたか?」ときいてまわるわけにはいきません。そこで本記事では以下のレギュレーションに基づいて「年末年始にコードを書いた人(=エンジニアに向いている人?)」を判定していこうと思います。

  1. コードを書いた日は必ずgithubに草が生えている」と考える。すなわち「年末年始にコードを書いた人」とは「年末年始にgithubに草が生えている人」とする。また現状デフォルトブランチにマージされておらず未反映の分は考慮しない。
  2. 年末年始は12/30 ~ 1/3 の5日間と定義し、この間に1度でもcontributeした人の割合を測定する。
  3. githubのアカウントは1人1つとし、アカウントは必ず使われているものとする(すなわち未使用垢やサブ垢などは考えない)

方針

上記のレギュレーションにもとづき、より具体的な方針を決めていきます。

サンプル数はどのぐらい取れば良いか

githubを利用しているdeveloperの数は2020年時点で5600万人を超えており[1]、全員を調べるにはかなりの労力がかかります。
そこで本記事では「ある程度正しい結果」を得ることを目標におき、githubのuserの中から1部をサンプリングして、サンプルから得られた結果を全体のものとすることにしました。

では、一体いくつのサンプルをとればその結果は「ある程度たしかである」といえるのでしょうか?
実は統計的には、母集団が100万人を超えていたとしても、384人分のデータがあれば、95%信頼できるデータがとれてしまうのです。[2]
本記事では400人分のgithub usersのデータをサンプリングして使うこととしました。

実装方針

1. github APIでusers一覧を取得

GithubのUsers APIを使って, githubのuser一覧を取得します。
Github Users APIではuserは最大100件ずつの取得しかできないため、100件ずつ取得するリクエストを計4回投げます
また、idは17027045(筆者のgithubアカウントにふられたid)から取得し、これを「無作為抽出されたデータである」と考えます。(超初期のgithubアカウントはgithub開発者のものや、すでに使われていないものが多いのではないかと考えたため)

2. 各userのcontributionsを解析

githubのcontributionのデータは以下のurlにアクセスすると取得することができます。

https://github.com/users/${user}/contributions

上記で取得できるページの、いわゆる「草」の部分をx-rayというスクレイピング用のライブラリを用いて解析することにしました。[2]

実装

実装方針に基づき、node.jsで以下のようなコードを作成しました。
http requestを投げる際にaxiosを、日付の比較の際にday.jsを、スクレイピングの際にx-rayを使用しています。

node.js
main()asyncfunctionmain(){constmyGitHubId=17027045//自分のgithubIdから400人分のgithub usersを取得users=awaitgetGitHubUsers(myGitHubId)//400人中年末年始にコードを書いていた人の数を集計constappropriateEngineerNum=awaitgetAppropriateEngineerNum(users)//結果console.log(`エンジニアに向いている人は${users.length}人中, ${appropriateEngineerNum}人です`)}//since番目から400人分のgithub usersを取得するasyncfunctiongetGitHubUsers(since){constaxiosBase=require('axios');constaxios=axiosBase.create({baseURL:'https://api.github.com',headers:{'Accept':'application/vnd.github.v3+json',},responseType:'json'})constusers=[]for(leti=1;i<=4;i++){response=awaitaxios.get('/users',{params:{since:since+i*100,per_page:100}})users.push(...response.data.map(user=>user.login))}returnusers}//引数のusersのうち、年末年始にコードを書いていた人数を算出するasyncfunctiongetAppropriateEngineerNum(users){constXray=require('x-ray')constx=Xray()letappropriateEngineerNum=0for(userofusers){constcontributionUrl=`https://github.com/users/${user}/contributions`constcontributions=awaitx(contributionUrl,'rect',[{count:'@data-count',date:'@data-date'}])constnewyearHolidaysContributions=contributions.filter((contribution)=>{returnisNewyearHoliday(contribution.date)&&contribution.count>0})if(newyearHolidaysContributions.length>0){appropriateEngineerNum++}}returnappropriateEngineerNum}//引数の日にちが年末年始かを判定するfunctionisNewyearHoliday(dateStr){constdayjs=require('dayjs')contributionDate=dayjs(dateStr)returndayjs('2020-12-29').isBefore(contributionDate)&&dayjs('2021-01-04').isAfter(contributionDate)}

結果

スクリーンショット 2021-01-06 22.28.51.png
ということで、「年末年始にコードを書いていた人」は 全体の2%という結果になりました。

補足

github profileのcotribution設定からは、private contributionの反映をオフにすることができます。したがって、この設定がオフになっており、かつプライベートリポジトリへのcontributionをした人はカウントされていません。

余談 ~「エンジニアに向いている人とは」~

最後に余談ではありますが、「どんな人がエンジニアにむいているのか」について私の見解をお話しします。

ツイッターやブログなどをみていると、「〇〇な人はエンジニアにむいていない」という意見が盛んに発進され、時に反発をうけているのを目にします。
私自身はこのどれもが「エンジニアに向いていない人」の条件となるとは思えません。なぜならまわりを見渡してみても「その条件を満たしてなくてもエンジニアとして働き、かつすごい技術を持っている方はたくさんいる」からです。(例えば上記の例に関しても、年末年始にコードを書いていなくともすごい技術力を持ったエンジニアはたくさんいます)。これらの意見を鵜呑みにし、自信を失ってしまうのはすごくもったいないことだと思います。

しかしながらその一方で「〇〇な人がエンジニアに向いていない」という意見は「自分はエンジニアである以上〇〇だけは譲れない」というプライドをもっていることの裏返しだとも思うのです。またそこに対して「それは違う」と感じてしまうのも自分の中でまた別のプライドがある証ではないでしょうか。

そして私はそのエンジニアとしてのプライドを持っている人こそが「エンジニアに向いている人」だと思うのです。

私自身はエンジニアとして「新しい技術に興味をもち」, 「コードを書くことを楽しむ気持ちを忘れない」ことをプライドとしてもっています。2021年もこの気持ちを大切に日々鍛錬していきたいものです。

参考文献

[1] he 2020 State of the OCTO—VERSE
[2] x-rayを使ってgithubの草の情報を取得する
[3] SurvayMonkey ~アンケートの標本サイズ~

Viewing all 8922 articles
Browse latest View live