language-detectをワンライナーで
そのソースファイルのプログラミング言語は何?を判別する実装がnpmにある。 https://www.npmjs.com/package/language-detect
これをワンライナーで適用する。事前にnpmモジュールはインストールして、
$ npm install language-detect
以下な感じで。
$ TARGET=$FILE node -e 'require("language-detect")(process.env.TARGET,(_,lang)=>{console.log(lang)})'
nodeって標準入力を扱うのが面倒くさいので環境変数経由で渡してる。
freeCodeCampを魔改造してSlackでOAuth2認証を可能にする
f821a35be730fb6cf73d75de5ff76ed0f75d9d6a 時点の staging ブランチを魔改造したよっていう話。
passport-slack のインストール
$ npm install --save passport-slack
Slack でアプリ作成
https://api.slack.com/apps にアクセスして新規アプリを作成する。 Redirect URI
がアプリをサービスする URL に対して作成する必要あり。ローカル用では以下を登録する。
http://localhost:3000/auth/slack/callback
App Credentials
から Client ID
と Client Secret
をコピって .env
に記述する。
diff
以下の通り。どこかのタイミングで github
でしか OAuth 認証をしないようにハードコーディングされているので、 slack
にハードコーディング上書きしている。プロフィール画像も決め打ち実装。
diff --git a/common/models/User-Identity.js b/common/models/User-Identity.js index 962fe0d..6cad516 100644 --- a/common/models/User-Identity.js +++ b/common/models/User-Identity.js @@ -72,7 +72,7 @@ export default function(UserIdent) { loopback.getModelByType(loopback.User); const userObj = options.profileToUser(provider, profile, options); - if (getSocialProvider(provider) !== 'github') { + if (getSocialProvider(provider) !== 'slack') { const err = new Error(createAccountMessage); err.userMessage = createAccountMessage; err.messageType = 'info'; @@ -130,7 +130,8 @@ export default function(UserIdent) { } const { profile, provider } = userIdent; - const picture = getFirstImageFromProfile(profile); + // const picture = getFirstImageFromProfile(profile); + const picture = profile && profile.user && profile.user.image_72; debug('picture', picture, user.picture); // check if picture was found diff --git a/package.json b/package.json index 2123144..a352f92 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "passport-linkedin-oauth2": "^1.2.1", "passport-local": "^1.0.0", "passport-oauth": "^1.0.0", + "passport-slack": "0.0.7", "passport-twitter": "^1.0.3", "pmx": "~0.6.2", "react": "~15.4.2", diff --git a/server/passport-providers.js b/server/passport-providers.js index 2b8aebf..cc8c796 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -162,5 +162,32 @@ export default { successFlash: [ 'We\'ve updated your profile based ', 'on your your GitHub account.' ].join('') + }, + 'slack-login': { + provider: 'slack', + module: 'passport-slack', + clientID: process.env.SLACK_ID, + clientSecret: process.env.SLACK_SECRET, + authPath: '/auth/slack', + callbackURL: '/auth/slack/callback', + callbackPath: '/auth/slack/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['identity.basic', 'identity.email', 'identity.avatar', 'identity.team'], + failureFlash: true + }, + 'slack-link': { + provider: 'slack', + module: 'passport-slack', + clientID: process.env.SLACK_ID, + clientSecret: process.env.SLACK_SECRET, + authPath: '/link/slack', + callbackURL: '/link/slack/callback', + callbackPath: '/link/slack/callback', + successRedirect: successRedirect, + failureRedirect: linkFailureRedirect, + scope: ['identity.basic', 'identity.email', 'identity.avatar', 'identity.team'], + link: true, + failureFlash: true } };
.env
追記
SLACK_ID
と SLACK_SECRET
を追記している。
Slack 認証リンク
View のどこかに以下を記述すれば OK。
a.btn.btn-lg.btn-block.btn-social.btn-facebook(href='/auth/slack') i.fa.fa-slack | Sign in with Slack
LoopBack 入門
門を叩きます。
インストール
$ npm install -g loopback-cli
アプリケーションの作成
$ lb _-----_ | | ╭──────────────────────────╮ |--(o)--| │ LoopBack │ `---------´ │ アプリケーションを作成しましょう。 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y `
なんか出たw
? アプリケーションの名前は何ですか? hello_loopback ? プロジェクトを格納するディレクトリーの名前を入力してください: hello_loopback create hello_loopback/ info 作業ディレクトリーを hello_loopback に変更します ? どのバージョンの LoopBack を使用しますか? 3.x (current) ? どのようなタイプのアプリケーションにしますか? hello-world (A project containing a controller, including a single vanilla Message and a single remote method) .yo-rc.json の生成中
メニューによっては上下キーで選択もできる。すごい。
いろいろモジュールがインストールされたら最後に以下の出力。
次のステップ: アプリケーションのディレクトリーに移動します $ cd hello_loopback アプリケーションでモデルを作成します $ lb model アプリケーションを実行します $ node . The API Connect team at IBM happily continues to develop, support and maintain LoopBack, which is at the core of API Connect. When your APIs need robust management and security options, please check out http://ibm.biz/tryAPIC
インストラクションに従ってモデルを作成する。
$ cd hello_loopback $ lb model ? モデル名を入力します: person ? person を付加するデータ・ソースを選択します: db (memory) ? モデルの基本クラスを選択します PersistedModel ? REST API を介して person を公開しますか? Yes ? カスタム複数形 (REST URL の作成に使用します): people ? 共通モデルですか、あるいはサーバー専用ですか? 共通 では、person プロパティーをいくつか追加しましょう。 完了したら、空のプロパティー名を入力してください。 ? プロパティー名:
モデルのプロパティを入力していく。
? プロパティー名: name invoke loopback:property ? プロパティー・タイプ: string ? 必須 Yes ? デフォルト値 [なしの場合は空白のまま]: 別の person プロパティーを追加しましょう。 完了したら、空のプロパティー名を入力してください。 ? プロパティー名: age invoke loopback:property ? プロパティー・タイプ: number ? 必須 Yes ? デフォルト値 [なしの場合は空白のまま]: 20 別の person プロパティーを追加しましょう。 完了したら、空のプロパティー名を入力してください。 ? プロパティー名:
モデルができたらアプリケーションを起動(!)
$ node .
起動したら http://0.0.0.0:3000/explorer/ にアクセス。
作成したモデルの POST
を開いて、適当なデータをPOST。
内部で curl とか叩いてくれているっぽい。
GET
を開いてリクエストすると作成した値が返ってきた。
うん、コード書かずに簡単な RESTful API ができてる。なお DB は設定していないのでアプリ再起動でデータは消える。DB設定すれば簡単に永続化もできそう。
モデル作成時のファイル差分
On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: common/models/message.json modified: server/middleware.json modified: server/model-config.json Untracked files: (use "git add <file>..." to include in what will be committed) common/models/person.js common/models/person.json no changes added to commit (use "git add" and/or "git commit -a")
common/models/
以下にモデル定義とモデルクラスのソースが出力される。
server/model-config.json
に列挙されたモデルの情報が入る。
$ git diff server/model-config.json diff --git a/server/model-config.json b/server/model-config.json index a432664..6b901f0 100644 --- a/server/model-config.json +++ b/server/model-config.json @@ -37,5 +37,9 @@ }, "Message": { "dataSource": null + }, + "person": { + "dataSource": "db", + "public": true } }
server/datasources.json
で DB の接続情報が書けそうだ。
$ cat server/datasources.json { "db": { "name": "db", "connector": "memory" } }
server/server.js
がエントリーポイントだろう。また server/boot/root.js
で middleware 設定してた。これは loopback-boot
関連なのかな。
$ cat server/boot/root.js 'use strict'; module.exports = function(server) { // Install a `/` route that returns server status var router = server.loopback.Router(); router.get('/', server.loopback.status()); server.use(router); };
server/component-config.json
で /explorer
の定義もあるな。
$ cat server/component-config.json { "loopback-component-explorer": { "mountPath": "/explorer" } }
全体のディレクトリ構成
$ ls client common node_modules package.json server
client
にはフロントエンドリソースを格納せよと書いてあったcommon
はモデル情報入っているserver
にはWebアプリ実装が入っている
いまさらながら Express 入門
こいつを調べようと思って、
Express ベースらしいので、最初はそっちから戯れる。
Hello Express!
$ mkdir hello_express $ cd hello_express $ npm init $ npm install express --save
$ cat app.js const express = require('express') const app = express() app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(3000, () => { console.log('Example app listening on port 3000!') })
req
と res
は Node 標準 API の HTTP のやつと同じ。
$ node app.js
$ curl http://localhost:3000/
Routing 基本
基本形。
app.METHOD(PATH, HANDLER)
GETとPOSTのルーティング。
app.get('/', (req, res) => { res.send('Hello World!') }) app.post('/', (req, res) => { res.send('Got a POST request') })
Static ファイルの配信
ビルドインの express.static
middleware を使う。
app.use(express.static('public'))
特定URLでマッピングするなら、 app.use()
の第1引数にパスを指定する。
app.use('/static', express.static('public'))
ファイルの探索はプログラム起動したディレクトリからの相対パスなので、絶対パスで指定するのがよい。
app.use('/static', express.static(path.join(__dirname, 'public')))
404 のハンドリング方法
全ての middleware と route を実行した結果、どれもレスポンスを返せなかった場合に 404 となる。なので一番最後に以下 middleware を定義すればよい。
app.use((req, res, next) => { res.status(404).send("Sorry can't find that!") })
エラーハンドリング
引数4つの middleware でエラーハンドリングとなる。
app.use((err, req, res, next) => { console.error(err.stack) res.status(500).send('Something broke!') })
Routing
この基本形。
app.METHOD(PATH, HANDLER)
METHOD
は HTTP メソッドと対応してて、めっちゃ定義されている。
get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search, and connect
app.all()
という HTTP メソッドと対応していないやつがあり、HTTP メソッド関係ない。特定パスに対してのみ middleware を使うのに等しいのかな。
app.all('/secret', (req, res, next) => { console.log('Accessing the secret section ...') next() // pass control to the next handler })
PATH
にはパターンや正規表現が使える。 ?
, +
, *
, (
, )
はパターンで処理される。
app.get('/ab?cd', (req, res) => {}) // `/abcd`, `/acd` app.get('/ab+cd', (req, res) => {}) // `/abcd`, `/abbcd`, `/abbbcd` app.get('/ab*cd', (req, res) => {}) // `/abcd`, `/abxcd`, `/abRANDOMcd`, `/ab123cd` app.get('/ab(cd)?e', (req, res) => {}) // `/abe`, `/abcde` app.get('/ab(cd)?e', (req, res) => {}) // `/abe`, `/abcde` app.get(/a/, (req, res) => {}) // `a` 含むもの全て
:
でURLの部分文字列がパラメータにマッピングされる。
app.get('/users/:userId/books/:bookId', (req, res) => { console.log(req.params) // /users/1/books/23:title] => { userId: '1', bookId: '23' } })
-
, .
はその文字のまま取り扱われる。なので、以下定義ではそれぞれ from
, to
および genus
, species
がキャプチャ対象。
/flights/:from-:to /plantae/:genus.:species
HANDLER
は複数指定できる。next()
を呼ばないと次のハンドラには行かない。
app.get('/example/b', (req, res, next) => { console.log('the response will be sent by the next function ...') next() }, (req, res) => { res.send('Hello from B!') })
Arrayでもよい。
app.get('/example/c', [cb0, cb1, cb2])
express.Router
というやつを使うと、いくつかの route や middleware 利用をまとめてモジュールとできる。
var express = require('express') var router = express.Router() // middleware that is specific to this router router.use(function timeLog (req, res, next) { console.log('Time: ', Date.now()) next() }) // define the home page route router.get('/', function (req, res) { res.send('Birds home page') }) // define the about route router.get('/about', function (req, res) { res.send('About birds') }) module.exports = router
var birds = require('./birds') app.use('/birds', birds)
middleware と route の実行順
定義した順。next()
を呼ぶ限り、次の middleware or route が実行される。
next
仮引数
次の middleware 関数ってことらしい。
next('route')
とすると、次の middleware へ飛ぶ(stack を辿らない)。以下は 1-1
と 2
が出力される。
app.get('/', [ (req, res, next) => { console.log('1-1') next('route') }, (req, res) => { console.log('1-2') } ]) app.get('/', (req, res) => { console.log('2') })
テンプレートエンジン
テンプレートエンジンは I/F が準拠してるものは全て使える。
app.set('views', './views')
でテンプレートファイルがあるディレクトリを指定するapp.set('view engine', 'pug')
でテンプレートエンジンを指定する
上記が設定されていれば、 res.render()
を呼び出しすればよい。
res.render('index', { title: 'Hey', message: 'Hello there!' })
エラーハンドラする middleware を複数
app.use(logErrors) app.use(clientErrorHandler) app.use(errorHandler)
logErrors では next(err)
として、次のエラーハンドラに err
オブジェクトを渡してあげる必要がある。つまり next()
に引数を渡すと次のエラーハンドラが実行されるってことらしい。
function logErrors (err, req, res, next) { console.error(err.stack) next(err) }
デバッグ
環境変数で出力有無を制御できる。
$ DEBUG=express:* node index.js
値の指定方法は debug モジュールってやつを利用してて、名前空間的なアプローチっぽい。
app.set()
設定値的なやつを設定すると middleware をまたいで値の共有ができる。
freeCodeCampのサーバ起動スクリプトを追いかける
アプリケーションサーバ起動方法がいくつかあるように見受けられるので、まずはそこらへんの整理から。
追いかけたブランチ
master 50a388d
gulp
ドキュメントに記載ある実行方法。gulpfileの中を覗いてみたところ、nodemon
使ってたりしてるのでローカル開発用かと。エントリーポイントはserver/server.js
なのを確認。
npm start
実態は babel-node server/server.js
。
npm start-production
名前的に production 実行用だろう。実態は node pm2Start
でpm2Start.js
が動いて、server/production-start.js
がキックされる。
server/production-start.js
ではDB接続を確認してからserver/server.js
を動かしているってことみたい。
server/server.js
というわけで、こいつがエントリーポイント。
利用されてるライブラリとかフレームワーク
以下らへんを別途調べていく。
loopback がメインな Web-App フレームワークっぽい。ルーティングとかどうやって解決されるか謎いので、ちょっとそっちを調べよう。
freeCodeCampのmasterブランチをローカルで動かす
こいつをカスタマイズしてローカルで動かしたくて弄っているログです。
master ブランチに docker-compose.yml が存在するので簡単にイケるんじゃね?と思ったのがスタートで、OSS Gate東京ミートアップ2017-02-20に参加した時にテーマとして選択してみた。ちなみにそのイベントでは動かせていない。
暫定版の起動方法
ごにょごにょした結果、以下の手順を踏めば起動まで持っていけた。
ちなみに staging ブランチが本家で動いているものを差異があるようだったので master ブランチで動かしているが、 docker-compose.yml は staging ブランチにしかコミットされてないのでそっちから持ってきている。
git clone https://github.com/freeCodeCamp/freeCodeCamp.git cd freeCodeCamp git checkout -b master-20170222 50a388d # `loopback` `debug` `normalizr` は固定しないとダメ perl -pi -e 's/"loopback": "\^2.22.0"/"loopback": "~2.22.0"/' package.json perl -pi -e 's/"debug": "\^2.2.0"/"debug": "~2.2.0"/' package.json perl -pi -e 's/"normalizr": "\^2.0.0"/"normalizr": "~2.0.0"/' package.json npm install bower install npm run build cp sample.env .env echo NODE_ENV=production >> .env git checkout staging -- docker-compose.yml docker-compose up
これで一旦 Web サーバーと MongoDB の 2コンテナが起動するので、別セッションで以下を実行すればチャレンジ(問題)がロードされる。
# データベースへロード docker exec $(docker ps -f name=freecodecamp_server_1 -q) node seed
DBの内容は起動時に読み込まれるっぽいので、一度 docker-compose を終了させて再起動すれば反映される。
docker-compose up
カスタマイズしたいポイント
- 言語切替 + 翻訳して日本語化
- OAuth認証の向き先変更
- ヘルプフォーラムの向き先変更
お前は何がしたいんだっていう話ですが、社内の研修リソースとしてうまいこと活用できないかなと目論んでいるわけです。本家にも貢献できればいいけれども、それは出来たらベースということで。
RSpec 3.2 has been released! されたので Notable Changes をメモ
これね。 http://rspec.info/blog/2015/02/rspec-3-2-has-been-released/
超絶抄訳です。
Windows CI
RSpec 3.1リリースした時にWindows上で動かなくしちまったらしい(3.1.xのパッチリリースでは直している)。なのでWindows環境でのCIを追加したぜ!という話。
Core: Pending Example Output Includes Failure Details
Pending exampleの出力にfailureメッセージ(?)を含むようにしたので、Pendingの詳細知りたい場合にわざわざコードを確認しなくてもおk。
Core: Each Example Now Has a Singleton Group
(うーん、日本語にうまく訳せないのだががが、)各Exampleからそれに対するExample groupの暗黙的な一部として扱うようにしたので、Example groupに対して適用されるメタデータがExampleにも適用されるとかなんとか。
Core: Performance Improvements
rspec-coreのオブジェクトアロケーションを30%ほど削減したのでパフォーマンスがアップ。
Core: New Sandboxing API
RSpec自身のテストのために利用されていたサンドボックス機能を公開APIにしたのでサードバーティなRSpec extensionsのテストでも利用できるぜっていう話。
Core: Shared Example Group Improvements
Shared example groupsのバグが直った!
- Shared exampleでのFailuresで正しくバックトレースを出力するようにした
- Shared exampleでのFailuresで出力されるre-runコマンドが正しくre-runされるようになった
- Shared exampleへの行フィルタが正しく動くようになった
Expectations: Chain Shorthand For DSL-Defined Custom Matchers
カスタムマッチャを定義するDSLでchain
が短く書けるようになった。
Expectations: Output Matchers Can Handle Subprocesses
Outputマッチャでサブプロセスの出力も検証可能になった。
Expectations: DSL-Defined Custom Matchers Can Now Receive Blocks
カスタムマッチャを定義するDSLで作ったマッチャがブロックを受け取れるようになった。block_arg
メソッドな。
Mocks: any_args Works as an Arg Splat
any_args
マッチャが賢くなって、部分的な引数リストに適用出来るようになった。
Mocks: Mismatched Args Are Now Diffed
モックの引数マッチャでdiffがpretty-printされるようになった。これは見やすい。
Mocks: Verifying Doubles Can Be Named
RSpec 3.0と3.1で追加したテストダブルタイプにオプション名をサポートするのを忘れてて(テヘペロ)、3.2でサポートされたぜ。という話。
Rails: Instance Doubles Support Dynamic Column Methods Defined by ActiveRecord
ActiveRecordベースのinstance_doubleではテーブルカラム名のメソッドははじめてコールされるタイミングで動的生成されるので、動的生成されるまでmethod_defined?
が意図した通りに機能せず分かりづらい。なのでinstance_double生成時点で解決されるようにした。
Rails: Support Ruby 2.2 with Rails 3.2 and 4.x
タイトルの通り :)
Rails: New Generator for ActionMailer Previews
Rails 4.1でリリースされたActionMailer previewsがデフォルトtest
ディレクトリに出力されて、RSpecはspec
ディレクトリを利用するのでうまく統合できてなかったので、RSpec 3.2でspec
ディレクトリに生成されるように統合したぜ。