OmniAICreator/Qiita-1.07M huggingface dataset:
全文検索サーバを構築した話 その1（dockerによる構築編）

# はじめに
自社で全文検索サーバの需要がありそうだなーとなんとなく感じていました。
理由としては、

* 社内に数TBの文書があるが、検索手段はWindows標準エクスプローラーの検索のみ。
* とにかく多い文書の中から、参考にしたい文書になかなかたどり着けないという意見が出ている。

それ以外にも細かい理由はあると思います。が、とりあえず上記課題を解決すべく検討を始めました。

## 1. 製品導入の検討
実は、昨年の今頃あたりからずっと全文検索サーバを探しておりました。
もちろん有償のパッケージもので。
なぜかというと、保守サポートないものを会社に入れたくないから。
なので、なるべくパッケージもので探してたのですが…

__パッケージものって高いのよねぇ…__

パッケージのみで1千万超えてくるものも普通で、それにサーバとSI費用が乗っかってくると…
現実味がなくて、パッケージものは検討をやめました。

## 2. OSSでの実装を検討
製品導入の検討をやめて、全文検索サーバについてはしばらく寝かせておいたのですが、
突然復活させざるを得なくなってしまいました。
なので、とりあえずOSSを組み合わせてどこまでできるか検討してみることにしました。

### 検討に使用した構成
|種別|名前|
|:-:|:-:|
|OS|CentOS 7|
|HW|Dell PowerEdge T110|
|CPU|Intel Xeon X3430|
|メモリ|4GB|
|HDD|500GB|
|全文検索|[Fess](https://fess.codelibs.org/ja/) + [ElasticSearch](https://www.elastic.co/jp/products/elasticsearch)|
昔使ったサーバが転がっていたので、これにCentOSを入れて検証環境を構築してみました。

## 3. 検証環境構築
まずは普通にOSインストール。
んで、FessとElasticSearchをyumでインストールしてみたんですが…
__どうやっても連携がうまくいかん…__
色々試行錯誤してみたんですが、どうにも原因がわからんしfessもElasticSearchも
慣れてないせいかアンコントローラブルなのでどうにもならなかったところに、
一つの光明が…

__「そういやdockerって使ったことなかったけど、dockerでFessの導入実績ってあるんかな？」__

ググってみたら、[こちら](https://qiita.com/cookienote/items/1f2443be25630a78d946 "全文検索サーバー Fess を Docker で動かす")の記事にたどり着きました。
さっそく導入をば。

```shell-session
$ sudo yum -y install docker
$ sudo docker pull codelibs/fess:latest
```
この2ラインだけで、dockerのインストールとfessのインストールまで終わります。
んでもって、

```shell-session
$ sudo docker run -d -p 80:8080 --name fess codelibs/fess:latest
```
で、コンテナを配置して起動。
ポートに関しては8080をフォワードせずに、ホストの80ポートをdockerコンテナの8080に
フォワードする構成にしてみました。
（今後の運用を考えるとURLをなるべく短くしたかったため）

これだけです。
OSを最小構成でインストールして、この3つのコマンドを実行するだけで、Fessサーバができました。

正直、目から鱗が落ちるとはこのことかと思いました。

__dockerやばい…__
__なんでこれを今まで触らなかったのか…__

FessとElasticsearchを入れて散々悩んだ挙句に動かなかったものが、ものの10分で環境ができてしまう。
さらに、コンテナの環境が汚れてきたらコンテナ削除してまたdocker runするだけでリフレッシュできてしまう。
本当に素晴らしい。
こりゃ散々話題になるわけですな…
完全に浦島太郎でしたが、今回使用してみて、dockerの素晴らしさをしみじみと感じました。

というわけで、とても簡単にFessによる全文検索サーバの構築が完了しました。

次回以降で、Fessの環境設定と全文検索の準備作業諸々も書きたいと思います。
野狐囲碁をUbuntuで

今おそらく最も栄えているネット囲碁プラットフォーム[野狐囲碁](http://webigojp.com/) にUbuntuからログインできたのでメモ。

方法はいたってシンプルで、公式インストーラ（exe形式）を[Wine](https://en.wikipedia.org/wiki/Wine_(software))を介して実行する。すべてのWindowsプログラムがWineで動くわけではないので、動いてくれたのは幸運だった。

ただし、チャットメッセージ内のフォントに文字化けが残っている。


## Wineのインストール

```bash
sudo apt-get install wine1.6-amd64
```

## 野狐のインストーラを取得

[こちら](http://webigojp.com//files/install.exe)からダウンロードする。

## インストーラをWineから実行

インストーラを右クリックして、「Open with "Wine Windows Program Loader"」を選択。
![Screenshot_2019-06-06_16-03-02b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/83f167ac-c3a9-1cdd-fce0-4da836ea0caa.png)

もしくは、コマンドラインで

```bash
wine install.exe
```

するとインストーラが起動するので、ダイアログにしたがっていけばインストールできる。

しばらくすると野狐囲碁へのログインページが表示された。チャット内の文字化けは直し方が分からなかった。
![Screenshot_2019-06-06_15-58-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/fd20f136-46f6-68f6-467c-45359eb8178a.png)

次回以降は、デスクトップに作られる「Fox Fo Server」というファイルをダブルクリックして起動できるようだ。
![Screenshot_2019-06-06_16-01-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80588/f6f98db6-d236-2908-33ca-87fb5822ed31.png)
Tesseract OCR の使い方 (Python)

次と同じことを Python3 で行いました。
[Tesseract OCR の使い方](https://qiita.com/ekzemplaro/items/f7dc38bb2a35be9f9f10)

Arch Linux でのライブラリーのインストール

```bash
sudo pacman -S python-pyocr
```

プログラム

```py:example01.py
#! /usr/bin/python
#
#	example01.py
#
#						Jun/06/2019
# --------------------------------------------------------------------
from PIL import Image
import sys

import pyocr
import pyocr.builders

# --------------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")
#
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
# The tools are returned in the recommended order of usage
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))
#
txt = tool.image_to_string(
    Image.open('hensel_gretel.png'),
    lang="jpn",
    builder=pyocr.builders.TextBuilder()
)

print(txt)
#
sys.stderr.write("*** 終了 ***\n")
# --------------------------------------------------------------------
```

実行結果

```text
$ ./example01.py 
*** 開始 ***
Will use tool 'Tesseract (sh)'
大 き な 思 の す ぐ 近 く に 、 本 と り か お か み さ ん と 侑
た ち と 一 細 に ほ ん で い ま し た 。 畑 の チ は ヘ ン ゼ ル で な
の 子 は ダ レ ー テ ル と い う 名 前 で し た 、 本 こ り に は ほ と
*** 終了 ***
```
Splunk Email Setting with Gmail

Splunkでのe-mail設定について
#ユースケース
・アラート結果をメールで飛ばしたい
・結果をさらっとemailで飛ばしたい などなど

#Splunk側での設定
###1：設定　--> サーバー設定　クリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/48fe543f-8b2b-91fd-e5f3-076367b400ee.png)
###2：サーバー設定 --> メール設定　クリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/ee663190-c2f2-64c7-10db-17922617a1da.png)
###3：メールサーバー設定　を入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/7740c181-8967-8d4e-425f-4b6aa0194b90.png)
メールサーバー設定以外の項目はお好みで設定
###4：検証：SPLからemailに結果を飛ばしてみる
SPL例）
index=_internal | head 1 | sendemail to=飛ばしたいemailアドレス@XXX.com format="html" server=smtp.gmail.com:587 use_tls=1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/6abf8ba1-bb3c-9d51-491c-f387a5d29363.png)
しばらくして以下のようなメールが届いていれば成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/b5f87124-3b67-0a02-8bc4-418290136841.png)

#うまくできないときのGmail側での設定
###1：安全性の低いアプリの許可: 有効
https://myaccount.google.com/lesssecureapps
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/e94bfab5-7444-0fa0-640b-278cf96d441f.png)
###2：Google アカウントへのアクセスを許可
https://accounts.google.com/DisplayUnlockCaptcha
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/995dcd61-83a9-224a-f115-ddea806c3388.png)
次へ　を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394817/93865ea9-5dab-26d0-e74a-274c9b5c8b75.png)

以上です。

以下Answersリンク
https://answers.splunk.com/answers/716894/email-configuration-setting-is-not-working-for-smt.html
LINE Botを作るときの用語や管理画面のまとめ

# 概要
ちょっとLINE Botでも作るかとなったときに、色々用語や管理画面がありすぎてわかりにくいのでまとめた。

# 用語集
|用語|説明|
|---|---|
|LINE Channel（チャネル）|LINE Botにあたるもの。LINEログイン、Messagin API、Clova Skillsの3種類がある。また、Channelを作るとLINE公式アカウントも自動で作られ、紐づけられる|
|LINE Provider（プロバイダ）|LINE Channelを束ねるもの|
|LINE公式アカウント (LINE Official Account)|LINE＠とLINE公式アカウントが統合されたもの。 https://www.linebiz.com/lineat_migration/|
|[LINE Developers](https://developers.line.biz/)|開発者向けサイト。ChannelやProviderを作る|
|[LINE Official Account Manager](https://manager.line.biz/)|LINE公式アカウントの管理サイト|
|[LINE@ Manager](https://admin-official.line.me/)|LINE Official Account Managerに移行された。 https://www.linebiz.com/lineat_migration/|
|[LINE Business Account](https://account.line.biz/login)|LINE Official Account Managerなどを使うためのアカウント。[ここ](https://account.line.biz/signup)からメアドのみでアカウント作れる|
|[LINE Business ID](https://www.linebiz.com/jp/faq/line-official-account/businessId/)|LINEビジネスIDとは、LINEが提供するビジネス向け、または開発者向けの各種管理画面にログインができる共通認証システムです。LINEビジネスIDには、LINEアカウントでのログイン、またはメールアドレスのみで利用できるビジネスアカウントでのログインがご利用いただけます。|
LINE Business AccountとLINE Business IDが紛らわしすぎる。

# トラップ
追記：3ヶ月たてば消えるようです（ @kuruumii さんありがとうございます！ ）

LINE DevelopersでChannelを削除しようとすると、LINE Official Account ManagerにてLINE公式アカウントを削除するように言われる。
そこでLINE Official Account ManagerからLINE公式アカウント自体を削除しても、LINE公式アカウント自体は消えず「このアカウントでは一部の機能が利用できません。(Functions for this account are currently locked.)」と出て機能制限の状態になる。Developersの方のChannelも消えない。なので使わないChannelとLINE公式アカウントがどんどん溜まっていくことになる。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68213/fc9f5063-6689-1017-1879-3918aabe0502.png)
ゼロから始めるPHP ～Laravel掲示板にメールアドレス＆パスワード認証機能をつける～

今回はタイトルの通り前回Laravelで作成した掲示板にメールアドレス・パスワードを使った認証機能を付けるという記事です。

laravelを使った掲示板のチュートリアルはこちら。

[laravel5.7でコメント機能付き掲示板を作るチュートリアル ](https://qiita.com/zeroPro/items/13868def907dced932bc)

掲示板を作成してから認証機能をつけるまでに悩みに悩んで2週間ほど経過しました。
昨日、laravelの機能を使った認証機能が正常に働くことが確認出来たので備忘録も兼ねて記事にしておきます。


<h2>参考にした記事・webサイト</h2>
公式リファレンスを参考にしました。
[Laravel5.7認証](https://readouble.com/laravel/5.7/ja/authentication.html)

<h2>認証画面の作成コマンド</h2>
laravelでメールアドレス＆パスワードを使用した認証画面を作成するコマンドは簡単です。


```ruby:ターミナル
php artisan make:auth
```

```ruby:ターミナル
php artisan migrate
```

この2つだけで完成します。

<h2>掲示板にメールアドレス＆パスワード認証機能を付与させる</h2>
さてここからどのようにして前回の掲示板に認証機能を付与させるかという事ですが、これも簡単です。

＊プロジェクト名をlaravel-bbsとします。
<h3>①付与したいプロジェクトに移動する</h3>

```ruby:ターミナル
cd laravel-bbs
```

<h3>②認証機能を作成する</h3>

```ruby:ターミナル
php artisan make:auth
```

<h3>③マイグレートする</h3>

```ruby:ターミナル
php artisan migrate
```

<h3>④認証画面の確認</h3>

```ruby:ターミナル
php artisan serve
/*実行結果*/
Laravel development server started: <http://127.0.0.1:8000>
```

で、laravelを起動させます。

ブラウザのURLバーに http://127.0.0.1:8000/login を打ち込みます。

すると以下の画面になります。
<img width="1504" alt="スクリーンショット 2019-06-05 23.15.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418206/5d02a6a2-d749-8a0f-8737-feee12d80057.png">



<h2>悩んでいたこと</h2>
<h4>問題点</h4>
php artisan make:auth ⇒ php artisan migrate　までは出来ていた。
php artisan serve でプロジェクトを起動させて、http://127.0.0.1:8000
をブラウザに打ち込むも認証画面が出てこない。
ずーっとこの画面が表示されます。
<img width="1680" alt="スクリーンショット 2019-06-05 22.02.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418206/55930bea-af5f-8b64-fd65-934a0a5d0e6e.png">




<h4>解決策</h4>
http://127.0.0.1:8000/login
「/login」を追記することでログイン認証画面をブラウザに表示させることができる。
この「/login」がネックでどこにも書いていなかったんだよなぁ・・・・

http://127.0.0.1:8000/register
「/register」を追記することで新規登録画面をブラウザに表示させることができた。
この「/register」は公式リファレンスに載っていた。


今回はアドバイスを頂き、この情報を知ることが出来ました。

<h2>次への課題</h2>
①プロジェクトのTOPページを認証画面に変更する
②ログイン中の人の名前をトップページに表示させる
②SNS認証機能を搭載する
④Herokuにプロジェクトをデプロイする
⑤AWS　EC2　RDBにプロジェクトをデプロイする（余裕があれば）
Tesseract OCR の使い方

次のページと同じことを、Tesseract で行ってみました。
[Microsoft Computer Vision API OCR の使い方 (日本語)](https://qiita.com/ekzemplaro/items/48b049411e7cefa81ff5)
まったく、学習はさせていません。

Arch Linux でのインストール

```bash
sudo pacman -S tesseract
sudo pacman -S tesseract-data-jpn
```

Ubuntu でのインストール

```bash
sudo apt install tesseract-ocr tesseract-ocr-jpn
```

入力画像
![hensel_gretel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/ec69d188-4166-b44f-1a1a-b13af18786f4.png)

実行結果

```text
$ tesseract hensel_gretel.png out01 -l jpn
Tesseract Open Source OCR Engine v4.1.0 with Leptonica
```

```text:out01.txt
大 き な 怪 の す く 近 く に 、 木 と り が お か み さ ん と 依
た ち と 一 細 に 住 ん で い ま し た 畑 の チ は ヘ ン ゼ ル で 如
の 子 は ダ レ ー テ ル と い う 名 前 で し た 、 本 こ り に は ほ 
```

次のバージョンで確認しました。

```text
Linux *** 5.4.7-arch1-1 #1 SMP PREEMPT Tue, 31 Dec 2019 17:20:16 +0000 x86_64 GNU/Linux
```
E2Eテストの自動化について調べてみた

# E2Eテストとは
ユーザがシステム上で行う一連のテストを最初から最後まで実施し、バグがないかテストします。
私は最後の砦と呼んでいます。


# テストのコストについて
## テストピラミッド
![1802F44A-AC26-49BC-A40A-D3752AB97ABA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137239/ae301549-9392-a5e5-12f6-4f35e62ab1c4.png)
引用元:[test piramid](https://martinfowler.com/bliki/TestPyramid.html)

UIテスト(E2Eテスト)は、速度が遅くコストも高いという話。
よく失敗談に出てくる失敗のケースが、ユニットテストレベルの内容ををUIテストで行おうとしたというのが出てくるので、テスト化する範囲は要注意

# E2Eテストの範囲
[Just Say No to More End-to-End Tests]
(https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html)


# 参考
[E2Eテストの導入から学んだこと]
(https://qiita.com/mt0m/items/7e18d8802843d9f60d28)
ActiveRecordでUPDATE|INSERT|DELETEのSQLを発行するたびにロックの情報をコンソールに出すおまじない

```ruby
locks =->{ arr=ActiveRecord::Base.connection.select_rows("select l.locktype, l.pid, c.relname, l.mode from pg_locks as l LEFT JOIN pg_class as c ON l.relation=c.oid WHERE c.relname not like 'pg_%'"); puts("--- LOCKS\n#{arr}\n---") }

ActiveSupport::Notifications.subscribe('sql.active_record'){|*args| locks.call if args.last[:sql] =~ /^(UPDATE|INSERT|DELETE)/ }
```
今更だけどPHPで複数のファイルアップロードをめっちゃシンプルに実装する

# いつもPHPでファイルアップロードについて調べちゃうのでまとめておく

そうなんですよね。こうした機能を実装する時に調べ時間が数分発生するので、自分用のメモとしてまとめておくことに。

ちなみに、最もシンプルな実装でエラーハンドル等も記述してない。そこは個々人のプロジェクトの環境(誰向けかとかね)やポリシ(エラーハンドルに関する)に従って真面目にやって欲しいと思います。ここでは語りません。

余談ですが、ユーザにファイルアップロードというか入力オペをさせるケースでは、その入力値検証を真面目にやるべきで、ファイルアップロードももちろん考慮が必要です。@mpyw さんがとても良くまとめられている[記事](https://qiita.com/mpyw/items/939964377766a54d4682)があるので是非参考になさってください。

また、正常系のエラーチェック(ファイル種別等による)は[こちら](https://www.flatflag.nir87.com/move_uploaded_file-970)のページが参考になります。


# PHPで複数ファイルのアップロード

```php:index.php
<?php

// ファイルがあれば処理実行
if(isset($_FILES["upload_file"])){

	// アップロードされたファイル件を処理
	for($i = 0; $i < count($_FILES["upload_file"]["name"]); $i++ ){

		// アップロードされたファイルか検査
		if(is_uploaded_file($_FILES["upload_file"]["tmp_name"][$i])){

			// ファイルをお好みの場所に移動
			move_uploaded_file($_FILES["upload_file"]["tmp_name"][$i], "./images/" . $_FILES["upload_file"]["name"][$i]);
		}
	}
}

?>

<html>

<head>
	<meta charset="utf-8">
	<title>ふぁいるあっぷろーどする</title>
</head>
<body>
	<form aciton="./" method="post" enctype="multipart/form-data">
		<input type="file" multiple name="upload_file[]" />
		<input type="submit" value="そうしん！" />
	</form>
</body>
```

HTML側のポイントは3点。

1. enctype="multipart/form-data" をformに設定しておく
2. input type="file"にmultiple属性を設定しておく
3. input type="file" のname属性はname="xxx[]"のように配列的にしておく

です。

サーバ側は、$_FILESに値がセットされてくるので、input[file]のname属性で指定したキーで値を抜いて、あとはループしてファイルをお好きなところに移動するだけです。

一番最初に書いた通り、ここではホントは色々エラーチェックしないと危ないので、必要に応じて処理を入れ込みます。

以上です。
bundle updateまとめ

### 本記事について
Railsの開発でよく使うbundle updateについて調べてみたのでまとめます。

### 目次

・bundlerとは
・bundle installとbundle updateの違い
・bundle updateのオプション紹介

### bundlerとは
プロダクト内部のgemを管理するためのgemです。
gemたちをまとめてくれる役割をします。これがあることで、```bundle update```や```bundle install```ができるようになります。

### bundle installとbundle updateの違い
[こちら](https://qiita.com/lasershow/items/1a048d03ddaaba98171e)の記事にかなり詳しく書かれていました。

上の記事とほとんど同じことを言いますが、**bundle installはGemfile.lockをまず見た上で、gemの更新やインストールを行います。bundle updateはGemfileをもとにgemの更新やインストールを行います。**

### bundle updateのオプション紹介

#### bundle update --all
Gemfileに指定されているすべてのgemを更新します。

#### bundle update --group=name, -g=name
指定されたグループのgenだけを更新します。
たとえば、```bundle update --group development```を使うとdevelopmentグループ内のすべてのgemを更新できます。
```bundle update rails --group test```によって、testグループ内のすべてのgemを更新することもできます。

#### bundle update --source=name
たとえばhttp：//github.com/rails/のようなものを呼び出すときに使います。
は```bundle update --source rails```によって、
```gem "rails", :git => "http：//github.com/rails/rails.git"```
のようにgemを書き換えます。

#### bundle update --local
リモートでgemを取得せずにgemのキャッシュを使います。

#### bundle update --ruby
ロックされているバージョンのRubyを現在のバージョンのRubyに更新します。

#### bundle update --bundler
ロックされたバージョンのbundlerを、呼び出されたbundlerのバージョンへ更新します。

#### bundle update --full-index
rubygemsの最近のインデックスを、APIのエンドポイントの代わりに使用します。

#### bundle update --jobs=number
並行して実行するジョブの数を指定できます。デフォルトは1ですが、```bundle update --jobs=10```のようにすると処理が早くなります。

#### bundle update --retry=number
失敗したネットワークリクエスト、またはGitへのリクエストを再試行する回数を指定します。

#### bundle update --quiet
警告とエラーのみを出力します。○○をやっています・・・といったような表示は出なくなります。

#### bundle update --force
すべてのgemを強制的にダウンロードします。 ```--redownload```はこのオプションのエイリアスなので、どちらを使っても構いません。

#### bundle update --patch
次のパッチバージョンへの更新だけを優先します。

#### bundle update --minor
次のマイナーバージョンへの更新のみを優先します。

#### bundle update --major
次のメジャーバージョンへの更新のみを優先します。

#### bundle update --strict
gemが最新のものを過ぎて更新されるのを許可しないようにします。

#### bundle update --conservative

他のgemのアップデートを抑制することができます。
```bundle update  --conservative rails```とするとrails以外のgemのupdateをしないようにします。

沢山あってまとめるのが大変でしたが、バージョンアップなどでお世話になるコマンドもいくつかありそうでした。
/etc/hosts形式のファイルをAnsible用インベントリファイルに変換するワンライナー

# これなに
- 以下の様に```/etc/hosts```があったとする。
    - 往々にして雑なコメントアウトがあったりする。

```txt:/etc/hosts
192.168.1.1       R1
192.168.1.2       R2
192.168.1.3       R3
# 192.168.1.4     R4 (現在設置中)
```

- これを以下のように変換したい

```txt:inventory_for_ansible
R1       ansible_host=192.168.1.1
R2       ansible_host=192.168.1.2
R3       ansible_host=192.168.1.3
```


# ワンライナー
- あっさり。

```bash:~/
cat /etc/hosts |  grep -v "#"  | awk  '{printf "%s \t ansible_host=%s\n",$2,$1}' > inventory
```
PHP: DateTimeZoneを使わずにDateTimeのタイムゾーンを指定する方法

PHPでは`new DateTime()`の第二引数に`DateTimeZone`オブジェクトを渡すとタイムゾーンを指定することができる:

```php
new \DateTime('2019-01-01 00:00:00', new \DateTimeZone('Asia/Tokyo'));
```

実は、第一引数にタイムゾーン識別子を入れると、上と同じことができる:

```php
new \DateTime('2019-01-01 00:00:00 Asia/Tokyo');
```

念の為同じかどうかを確認してみよう:

```php
date_default_timezone_set('UTC');

$date1 = new \DateTime('2019-01-01 00:00:00', new \DateTimeZone('Asia/Tokyo'));
$date2 = new \DateTime('2019-01-01 00:00:00 Asia/Tokyo');
assert(
	$date1->getTimestamp() === $date2->getTimestamp() &&
	$date1->getTimezone()->getName() === $date2->getTimezone()->getName()
);
```

現在時刻がタイムゾーン指定でほしいときは、タイムゾーン識別子だけ渡せばいい:

```php
var_dump(new \DateTime('Asia/Tokyo'));
```

出力結果:

```php
object(DateTime)#3 (3) {
  ["date"]=>
  string(26) "2019-06-06 14:45:00.377395"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
```
Golangガイドライン 1.10系

***attention: in this case go version 1.10.x***

# Go guideline
In our case.

## go setup

### Go install
https://golang.org/dl/
- If you want to use a different version, use a symbolic link or mod $PATH.
```
$ mv [downloadPath]/[downloadPKG] $HOME/
$ tar -xzf [downloadPKG]
$ mv go/ go1.xx.x
```

### path setting
```
$ mkdir $HOME/go
$ echo 'export GOPATH=$HOME/go' >> ~/.bash_profile
$ echo 'export GOROOT=$HOME/go1.xx.x' >> ~/.bash_profile
$ echo 'export PATH=$PATH:$GOROOT/bin' >> ~/.bash_profile
$ source ~/.bash_profile
$ go version // check
go version goX.XX.X darwin/amd64
```

### github clone dir
`$GOPATH/src/github.com/[username]/[appname]`

```
$ cd $GOPATH/src
$ mkdir -p XXX/AAA/DDD
$ git clone XXX/AAA/DDD XXX/AAA/DDD
```

### text editor
vim?
VScode?
JetBrains?
...as you like!

## tutorial
- [A Tour of Go](https://go-tour-jp.appspot.com)

## GHE rule
easy Git-flow.
You know, right?

### branchs
- master
- staging
- develop
- feature/*
- hotfixes/*

Don't do it! :
``` rebase ```
``` git push -f ```

=======================================
ここからにほんご。
いろんな文化のよいとこ取り入れていきたい。

## PR rule
わからない・知らないものほど積極的に参加し、わかりみ深めましょう。
みんな自分のタスクを抱えています。レビューし易いPRを心がけましょう。
例）モデル部分だけ、特定ロジックだけ等、粒度に気をつける。

- 件名
  - 成果物の名前等、わかりやすいものにする
- 説明
  - [#{数字}]で対応Issueを明記
- Labels
  - 必要に応じて選択
- Milestone
  - 対象のフェーズを選択

## styleGuide
- 前提として、`gofmt`,`goimports`,`golint`を使用してPUSHすること(Editorプラグイン推奨)
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
  - [日本語訳](https://qiita.com/knsh14/items/8b73b31822c109d4c497)

### パッケージ依存解決ツール
【~1.10.x】
`go get`しない。
[golang/dep](https://github.com/golang/dep)つかう。

【1.11.x~】
GoModulesつかおう。

### イディオム
- 宣言のグルーピング
  - `const`や`var`をカッコでグルーピングする際、用途などで個別にグルーピングすること
  - `struct`や`interface`はネストせず、定義を分割すること
- 関数のparamは迷ったらポインタ渡しにする
  - フィールド増えてもパフォーマンス落ちない
- パース用の構造体など、共通で使うpackageはimportを減らす。
- トランザクションなどの定型処理は匿名関数使おう。[いけてる例](https://stackoverflow.com/questions/16184238/database-sql-tx-detecting-commit-or-rollback)

### ログ・エラーハンドリング
- err.Error()を分岐に使うのは止めよう
- ログ出力は(出来る限り)各ハンドラーのトップレベルの関数`func(w http.ResponseWriter, r *http.Request)`で行うこと
- あちこちでログだすの止めよう
  - 下位の関数でエラーが発生した場合、ラップしてから上位の関数へエラーを返す
  - ラップすることでエラーが発生したファイルや行数を記憶してくれるため、上位の関数でログ出力してもエラーの発生箇所を特定できる
    - [ラップ時に利用するパッケージ](https://github.com/pkg/errors)
    - ラップの記述方法: `errors.Wrap(err, "error message")`
- ロガーは[clog](https://github.com/go-clog/clog)などを用い、POST特定用にリクエストIDなどあると望ましい。

### スレッドセーフ
- 複数のgoroutineで同一の変数を読み書きする際にはチャネルを使って同期をとったり、ロック`sync.Mutex`を使って排他制御をすること
  - [参考記事](https://qiita.com/ruiu/items/54f0dbdec0d48082a5b1)

## Test
- 疎結合を意識しよう
  - 外部と接続する設定を切り替え（envファイルなど）
  - 外部と接続するクライアントを切り替え（gomockなど）
  - 外部と接続する処理を切り替え（interface）
- 可能な限りTableDrivenTestsの形式で記述すること(可読性がよくなるため)
  - [TableDrivenTests](https://github.com/golang/go/wiki/TableDrivenTests)
個人的に興味・印象を持ったAI・データ分析ニュース

とあるきっかけで **最近興味を持った or 面白いなと思ったAI・データ分析関連のニュース、サービスについて** 調べてみる機会があり個人メモでまとめていたのだけど、せっかくなので投稿してみるという試み。

---
2019/4にあった、前回の[AI・人工知能EXPO](https://www.ai-expo.jp/ja-jp.html)から、流行りを感じたもの

# チャットボット

## チャットボットの種類

```plaintext
1.選択肢タイプ
データベースに蓄積されたシナリオや、設定された回答を選択して会話するタイプです。設定されていない受け答えはできません。

2.ログタイプ
会話を行った記録をログとして蓄積し、これを利用して人間に近づけた会話を行うタイプです。ログが蓄積されることによって、より自然な会話ができるようになるため、ログが少ない場合は会話が続かなくなります。

近年では、ログ解析にAIを活用し、人間の会話により近づける試みがされています。

3.ハッシュタイプ
辞書に登録されたテンプレートを元に、会話を行うタイプです。辞書タイプと呼ばれることもありますが、範囲の限定された利用方法であれば、受け答えには問題ありません。

4.Elizaタイプ
「Yes」「No」や相づちで返答しつつ、相手の言葉を要約したり聞き返したりすることで会話するタイプです。チャットボットの原型ともいわれるElizaから名付けられており、基本的には聞き役に徹するチャットボットといえます。
```

## 所感

自身ではヤマト運輸のLINE再配達ボットを利用したことがあるが、選択肢もしくはハッシュタイプの印象。
機械学習が活用されるのは「ログタイプ」と思われるが、どのように導入していくか。
また、どのようなシーンで需要が見込まれるか？
- チャット応答の内容を継続的に改善する（モデル改善）必要がある場合
- ユーザセグメントが時間と共に変化する場合
- ???

LINE、Facebook messenger などチャットプラットフォーム上へ展開するケースにチャットのログを収集できるか？
NLP

# AI人材

[夢見る「AI人材」じゃいられない](https://tech.nikkeibp.co.jp/atcl/nxt/column/18/00166/052000031/)

> 2019年5月18日付の日本経済新聞に載った「AI人材教育　国が認定」という見出しの記事では「AIなど」が7回繰り返されていた。
> 記事を読むと「AIやロボットなどの技術革新」あるいは「AIや数理、データサイエンスの分野」「AIやデータ分析」と書かれている。つまり「AIなど」の「など」にはロボット、数理、データサイエンス、データ分析が入り、さらにそれらを使ったイノベーションも含む。

一言に"AI人材" では括れない。
様々な専門家の集合知が必要になるのだから、特定領域の集中習得は必須。ただし テリトリーを広げるための努力も必要。
![データ活用人材に求められるスキル](https://www.hj.sanno.ac.jp/cp/incompany-training/img/ds/datas-skill.jpg)

---

# その他気になった技術・ニュース

## [オープンストリームとSBクラウド、ECサイトに画像検索を導入できるPoCサービスを開始](https://it.impressbm.co.jp/articles/-/17852)

Google 画像検索便利だなーと思っていたが、ECシステムに組み込むの良さそう。
新製品に対応していくための定期的なモデル改善が必要になりそう

> なお、Image Searchは、中国アリババグループが独自に開発した、マシンラーニング（機械学習）とディープラーニング（深層学習）技術を活用した画像検索エンジンである。中国のB2Cオンラインショッピングモール> 「天猫（Tmall）」や、CtoCマーケットプレイス「淘宝（タオバオ）」をはじめとするECサイトなどで使われている。

やっぱAlibaba すごい。

[メルカリでも似たようなのあった](https://it.impressbm.co.jp/articles/-/17679)

> 写真を撮るだけで出品できるようにする

すごい。
自社にデータもっててモデルを回せるプラットフォーマーはやっぱり強いなと。

### [時系列データからAI学習用の教師データを簡単に作成、富士通研究所と熊本大学が開発](https://it.impressbm.co.jp/articles/-/17885)

教師あり学習における教師データの生成を助ける試み。

### [ABEJAが「BERT」を活用した新サービス「ABEJA Insight for Contact Center」を発表](https://ledge.ai/abeja-insight-for-contact-center/)

```plaintext
従来、コンタクトセンターで用いられてきた自然言語（話し言葉や書き言葉）の処理モデルでは、画一的な対応しかできず、口語ならではの文法の揺らぎには的外れな回答をすることもあった。

そこで、2018年にブレイクスルーをもたらした「BERT（※）」のモデルをコンタクトセンターに利用することで、膨大な文書から複雑な言語表現の学習が可能になり、違和感のほとんどないやりとりが実現できると考えられるという。
```

```plaintext
※BERT（Bidirectional Encoder Representations from Transformers）
2018年秋に Google™️ がオープンソースとして公開した自然言語の意味理解に特化したモデルの名称
```

BERT 知らんかった。

[Googleが公開した自然言語処理の最新技術、BERTとは何者なのか](https://ai-scholar.tech/text-mining/bert-ai-93/)

> BERTは何がすごいのか
> BERTはこれまで用いられてきた自然言語処理タスクを解くモデルに直接接続できる、汎用的な分散表現計算機構として評価されています。

やばい。「何がすごいのか」を全く理解できんかった。
さらに調べる。
[なぜ自然言語処理にとって単語の分散表現は重要なのか？](https://qiita.com/Hironsan/items/a58636f946dd51f670b0)
ディープな話になってきた。
単語と単語の類似度を測れる点がone-hot表現よりも優れているということらしい。
で、RNNやCNNといったニューラルネットワークで自然言語処理する際に分散表現を使ってデータをINPUTすることが多いらしい。

「AI × コールセンター」はさらに市場規模が大きくなりそう。
[コールセンターの未来を変える?!　AIの行方【働く人の生の声】](https://callcenternavi.jp/ccplus/dictionary/6759/)
JSAI2019 ２日目まとめ

人工知能学会全国大会 JSAI2019の２日目に参加しに新潟まで行ってきました.
![image0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431433/4d32105e-2dd0-7827-2c6a-27f4675e5f04.jpeg)
![image1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431433/6fc59918-c48d-55be-6834-7cc9f001bc51.jpeg)
新潟といえば日本海から取れる美味しい回線と米所故の美味しい日本酒！
ホテルのクソwifiにイラつきながら,自分用の備忘録になりますので乱文となりますがご了承くださいな

二日目は私の興味がある分野についてセッションが少なかったので、いっそのこと色々見てまわろうということで全く知らない分野に行ってみました。

##1.[2M3-J-13-01] AI自動会計による無人店舗の推進
〇松尾 佳苗1、笠谷 奏乃1、石田 朋己1、坪田 凛々花1 (1. 同志社大学)
#####https://confit.atlas.jp/guide/event/jsai2019/subject/2M3-J-13-01/tables?cryptoId=
ホテルに忘れ物をしたせいで遅刻しつつの２日目最初のセッション聴講.
今回の学会発表で異質なものでした.というのもこの発表は統計でAIに対する忌避感を論ずるものでした.
価格の低い店舗では人よりもAIを好み,価格の高い店ではAIよりも人を好む.
価格の高い店でAIよりも人を好む理由は会話を楽しみたい、AIの相談は信用できない.
しかし4°Cという高級志向店舗でchat botによる対話型商品推奨を行ったところ,購入率が人が推奨した時よりも1.53%から4.59%と上昇したそうです.
ただし,AIまでの会話に至った層が20%と少ないので少し疑問が.もともとAIに対して抵抗の少ない人のみが利用し,その層はもともと購入率が4.59%よりも高かったかもしれません.
まあもともと価格の高いお店では接客対応にお金を割けるのであまりsolutionとして重要ではない気もしますが,こういう考え方は面白くて聴講してて面白かったです.
あと面白かったのが,質疑応答で「スライド内で引用していただいたサービスを開発した者なのですが」っていう質問者が居ました.
ひえっ.


##2.マーケットデザイン：ゲーム理論による社会制度の設計
横尾 真1 (1. 九州大学 大学院システム情報科学研究院 情報学部門)
#####https://confit.atlas.jp/guide/event/jsai2019/subject/2F3-OS-5a-03/advanced
ゲーム理論について最近勉強し始めたので,それについての講演があったので聞きに行って来ました.
ゲーム理論においてプレイヤーはお願いした通りに動いてくれる便利な存在ではありません.
全体での利益を追求するため,プレイヤーを動かすにはどうすればいいか? それはプレイヤー各自の利益を用意することで,うまく社会全体（総和）での利益を生み出そうとするのがゲーム理論によるマーケットデザインだそうす.
ゲーム理論によるマーケットデザインについて簡単に話があったのですが,その中でメインの題材になって居たvickrey入札という手法.
同時入札金額公開方式の落札において,もっとも社会的利益が出るように考え出された方法だそうです.
「一番高い金額を提示した人が落札者だが,支払う金額は２番目に高い金額」
といったものだそうです.
この手法で落札者が思う真の価値そのままで入札を行うようになり、全体としての利益も最大化されるそうです.
ただこの手法,実際にはあまり使われていなかったらしいのですが,近年において使われ始めました.
みなさんご存知のgoogleの検索広告で採用されたそうです.
googleの広告はオークション方式でよりtopに表示されるように競います.
しかし広告主がそれぞれギリギリの金額しか払わないようにより低価格へと競争し始め,価格が安定しなくなったそうで.
そこでvickrey入札を使用することで価格を安定化したそうです.
ただしgoogleは今年度から第一封印落札に戻ったそうです。他の解決方法があったんでしょうか.私気になります.

##3.ネット炎上におけるユーザーの共振構造
〇小山 耕平1、浅谷 公威1、榊 剛史1、坂田 一郎1 (1. 東京大学)
###https://confit.atlas.jp/guide/event/jsai2019/subject/2E5-J-6-02/tables?cryptoId=

ツイッターにおいての炎上を分析したというレポートです.
既存研究でredditという欧米で展開している掲示板サイトで炎上に多く参加している高頻度炎上参加ユーザー群(以下ユーザー群と呼ぶ)の存在が明らかになったそうです.
この研究はその高頻度炎上参加ユーザーに焦点を絞り、さらにまだ研究されて居ないtwitterにおいて研究したそうです.
すると実際にtwitterにもユーザー群が存在し,さらにそのユーザー群でネットワークを構成したところ６人のハブユーザーの存在が見つかったそう.6人中1ユーザーはmedia、３ユーザーは認証ユーザー（著名人等が公式から本人と確認されたユーザーのこと).
ここで発表内でこのユーザーたちについてコメントは特になく,気になったので質問したところ「このユーザー群には強い主張をもつ傾向があった」そうです.プロフィール欄に反原発等のコメントがずらっとあったそうで（どこの香山○カかよ）.
それとユーザー群はフォロワーが多い傾向が見られるそうで.まあそれは考えてみれば当たり前かなと.なぜなら「高頻度に炎上に参加する」ならば「ツイッターで積極的に活動している」,「ツイッターで積極的に活動している」ならば「必然的にフォロワー数が多くなる」と予測できます.


ということで私が特に面白いと感じた発表３件でした.
興味のない分野でもこうも面白いと感じられる発表が多く,学会というのは楽しい場所だなって思いつつ,明日に備えておやすみなさい.

###[3日目に進む!](https://qiita.com/MrMoto/items/f291bba4aac0a32afeb1)
pyenvで3.7系のインストールエラー時の対処メモ(Ubuntu)

## pyenv install 3.7.xでエラー(Ubuntu)

```
ModuleNotFoundError: No module named '_ctypes'
Makefile:1130: ターゲット 'install' のレシピで失敗しました
make: *** [install] エラー 1
```
と怒られる


## 対処法

[ドキュメント](https://github.com/pyenv/pyenv/wiki/common-build-problems)に
必要な環境が書いてあります.
まず言うとおりにインストールする.　

さきにaptをアップデートしておく。

```
$ sudo apt update
```

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

```
$ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
```

ここで、コンピュータを再起動して、pyenv installを再度試みる

```
$ pyenv install 3.7.3
```
 
行けました！！

ちなみにOpenSSLで下記のように怒られる場合は、

```
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?
```

opensslをアップデートしてみましょう。　　

```
$ sudo apt update && sudo apt upgrade openssl
```

それでも
  
```
ModuleNotFoundError: No module named '_ctypes'
```
とエラーになる場合でかつ、Homebrewを使っている場合、

上記でapt-get install したライブラリ群に関連する何かを
Homebrewでインストールしてしまっているのが原因の一つになります。

関連ありそうなものをuninstallしましょう。

```
$ brew uninstall <ライブラリ名>
```

全部uninstallする場合は下記で一発でいけます。

```
$ brew list | cat | xargs brew uninstall -f
```

もう一度、pyenv installを試みましょう
PlayCanvasのエディター上でHTML, CSSを組み込む方法

## 概要
- PlayCanvasのコードエディターはブラウザ上で開発することになるので少しつらい
- Web開発者とゲーム開発者の溝がかなりありそう。
- ビルドの仕組みを考えている。

## 前提
PlayCanvasはゲームエディターで基本的な開発はこのウェブのブラウザ上のエディターで完結されます。
1ヶ月ほど開発をしてみたのですが、基本的な開発フローとしては3つほどの流れで行いました。

1. 3Dモデル・素材を作成
2. PlayCanvas上でカメラの位置の調整、テクスチャの設定
3. デプロイ


PlayCanvasのエディターの中で開発をしている分にはエディターなどが整備されておりますが、デプロイの時においてウェブに組み込む際には、PlayCanvas上でDOM操作をしてPlayCanvasの実行時にDOMを被せる or PlayCanvasビルド時に落ちてきたHTMLファイルをiframe上で動かす方法が多く見られます。

公式のサンプルにあるこちらのページはCanvasの上にかぶせて表示
[BMW i8 - PLAYCANVS](https://playcanv.as/p/RqJJ9oU9/?overlay=false)

こちらのページはiframeを表示している
[VOXCELCANVAS](https://voxelcanvas.me/create)


## PlayCanvasのゲーム上にWebの要素を組み込む  

HTMLを組み込む対象として、先日行われた[2Dハンズオン](https://playcanvasjp.connpass.com/)のサンプルを使用します。

ハンズオンで使用したサンプルこちら[2Dハンズオンチュートリアル](https://support.playcanvas.jp/hc/ja/articles/360001806388)


<img src="https://user-images.githubusercontent.com/39250588/59009254-c46d0980-8867-11e9-85e5-6685d717f165.PNG" />


## 起動


<img src="https://user-images.githubusercontent.com/39250588/59009439-8de3be80-8868-11e9-8e9e-6515bda529e1.PNG" />

少し寂しいですが、このまま使用します。


## HTML/CSSを追加

1. 空のEntityに対してスクリプトを追加をして好きな名前で保存  
<img src="https://user-images.githubusercontent.com/39250588/58940359-3e43bb00-87b4-11e9-9598-d118ca8535c3.PNG" />

### 起動時にHTMLとCSSを追加するスクリプトを追加します。

```ui.js
// 一つのCSSを追加する方法
/*jshint esversion: 6, asi: true, laxbreak: true*/
const Ui = pc.createScript("ui");

Ui.attributes.add("css", {
  type: "asset",
  assetType: "css",
  title: "CSS Asset"
});
Ui.attributes.add("html", {
  type: "asset",
  assetType: "html",
  title: "HTML Asset"
});

Ui.prototype.initialize = function() {
  const body = document.getElementsByTagName("body")[0];
  const head = document.getElementsByTagName("head")[0];
  const style = `<style>${this.css.resource}</style>`;
  body.insertAdjacentHTML("afterbegin", this.html.resource);
  head.insertAdjacentHTML("afterbegin", style);
};


```

```ui.js
//複数のCSSを追加する方法
/*jshint esversion: 6, asi: true, laxbreak: true*/
const Ui = pc.createScript("ui");

Ui.attributes.add("css", {
  type: "asset",
  assetType: "css",
  title: "CSS Asset",
  array: true
});
Ui.attributes.add("html", {
  type: "asset",
  assetType: "html",
  title: "HTML Asset"
});

Ui.prototype.initialize = function() {
  const body = document.getElementsByTagName("body")[0];
  const head = document.getElementsByTagName("head")[0];
  const style = `<style>${this.css.map(({resource}) => resource).join("\r\n")}</style>`;
  body.insertAdjacentHTML("afterbegin", this.html.resource);
  head.insertAdjacentHTML("afterbegin", style);
};
```


### HTML・CSSを追加
好きなHTMLを追加
<img src="https://user-images.githubusercontent.com/39250588/59008778-4e1bd780-8866-11e9-83cd-57e227f5abfc.PNG" />

これで再びLaunchをすると起動時にHTMLが追加されます。
<img src="https://user-images.githubusercontent.com/39250588/59008585-e6658c80-8865-11e9-9fc4-3773d48259e7.PNG" />
terraform-provider-zendesk

TerraformでZendeskの構築を行うためのProviderプラグイン, [terraform-provider-zendesk](https://github.com/nukosuke/terraform-provider-zendesk)の日本語版READMEです。

**追記 2022.04:**
プロバイダを [Terraform Registry](https://registry.terraform.io/providers/nukosuke/zendesk/latest) に公開しました。(→[ドキュメント](https://registry.terraform.io/providers/nukosuke/zendesk/latest/docs))


-----

Terraform provider for Zendesk

- [利用可能なリソース](https://registry.terraform.io/providers/nukosuke/zendesk/latest/docs)

## 動作環境

- Terraform >= v0.13.x
- Go >= v1.17 (ビルドする場合のみ)

## インストール手順
terraform-provider-zendesk は [Terraform Registry](https://registry.terraform.io/providers/nukosuke/zendesk/latest) からインストール可能です。
以下のようにリソースファイルにプロバイダの設定を記述してください。

```hcl
terraform {
  required_providers {
    zendesk = {
      source  = "nukosuke/zendesk"
      version = ">= 0.0"
    }
  }
}
```

## プロバイダ開発者向け
### ソースをビルドする場合

```sh
$ git clone git@github.com:nukosuke/terraform-provider-zendesk.git
$ cd terraform-provider-zendesk
$ go mod download
$ go build .
```

## ライセンス

MITライセンス
詳細は[LICENSE](./LICENSE)をご覧ください。

## 関連パッケージ
- [nukosuke/go-zendesk](https://github.com/nukosuke/go-zendesk)
rspecでDBコネクションエラーが発生するようになった話

# 何が起きたか
rspecのテストケースが増えてきたある日、下記のようなdbコネクションに失敗する事象が発生するようになった。
毎回発生するわけではなく、エラーになるテストも毎回異なるという謎挙動。。。

```
ActiveRecord::StatementInvalid
(Mysql2::Error::ConnectionError: Lost connection to MySQL server during query: BEGIN):
```

railsなどのバージョンは下記

```
# rails -v
Rails 5.2.2.1
# ruby -v
ruby 2.5.3
```

# 最初に結論
active storageを使った機能のテストがエラーの原因だった。
active storageでファイルを追加するとファイルを分析する処理が非同期で動いており、DBコネクションを取り合ったりロックしたりしてエラーが発生していた。

# 調査
## エラー箇所特定
エラーになるテストケースと直近で触った機能を洗い出したところ、active storageを使った箇所が怪しいことが判明。
active storageを使っている箇所を中心に調査開始。

active storageを使った処理のログを眺めているとファイルをアップロードしたときに非同期で処理が動いている痕跡があった。
そこでソースをみたところ、アップロードしたファイルを解析する処理が非同期で動いていることが判明。
該当箇所は下記。
https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activestorage/app/models/active_storage/blob/analyzable.rb#L37

## 非同期を解消するには
非同期を解消するにはactive_job.queue_adapterを:inlineに変更すればOK。
こうするとactive jobも同期処理として実行される。

```ruby:config/environments/test.rb
config.active_job.queue_adapter = :inline
```

## 同期処理になったが、、、
DBコネクションエラーは発生しなくなったが、次は下記のエラーが発生するようになった。

```
MiniMagick::Error:
You must have ImageMagick or GraphicsMagick installed
```
これはテスト環境だけImageMagickが入っていなかったために発生していた。
今まで非同期だったのでrspecはエラーにならず気づけていなかった・・・

## 最終的にどうしたか

ImageMagickをテスト環境に入れるだけでエラーは解決できるが、最終的にはactive jobの実行メソッド(perform_later)を一律スタブにすることにした。

```ruby:spec/spec_helper.rb
RSpec.configure do |config|
  ・・・
  config.before(:each) do
    allow(ActiveJob::Base).to receive(:perform_later).and_return(true)
  end
end
```
このように対応した理由は、今回問題となった非同期処理は実行されなくてもテストに影響がないためスタブでも問題ないと判断したため。スタブにすることでテスト時間の短縮にもつながる。
また、自前のジョブを呼び出す機能をテストするときはジョブを呼び出すところまでをテストして、ジョブは別途テストを書くことで対応する。
paramsでネストした値を受け取るときにnilエラーを回避する方法

##一つ目のキーがnilでエラーになってしまう問題

渡ってくる`params`

```
<ActionController::Parameters {}>
```

`[user] key`の`[info] key`の値を取得したい。

```
info = params[:user][:info]
```

そもそも一つ目のキー(`user`)が存在しないのでエラー吐く。

```
undefined method `[]' for nil:NilClass
```

このようなエラーを回避するため、
**一つ目のキーがnilじゃない場合に、二つ目のキーを取得しに行いきたい**ときがあります。

やり方は色々ありますが、結論からいうと、`dig`メソッドを使うことをおすすめします。

理由は後半で説明したいと思います。

##1. &&で回避

一般的にはこの書き方で回避することが多いと思います

```rb
params = { }
info = params[:user][:info] if params[:user] && params[:user][:info]
# => nil
```
エラーは回避できますが、以下のように、ネストが増えるたびに記述が冗長になってしまい、見辛いのが本音。

```rb
params = { user: { info: { name: { first: :Suzuki } } } }

params[:user][:info][:name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:name] && params[:user][:info][:name][:first]
# => :Suzuki

params[:user][:info][:full_name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:full_name] && params[:user][:info][:full_name][:first]
#=> nil
```

##2. tryで回避
少し慣れない書き方ですが、paramsで`try`を使うには、以下の書き方でOK。

```rb
params = { }
info = params[:user].try(:[], :info)
# => nil
```

だいぶすっきりになりましたね。
`[:user][:info]`が存在すればその値を返し、
もし`[:user]`が`nil`だったとしても、エラーを回避して`nil`を返してくれます。

**これで、どれだけ複数ネストしていてもエラーを吐くことなく取得することが可能になります**

```rb

params = { user: { info: { name: { first: :Suzuki } } } }

info = params[:user].try(:[], :info).try(:[], :name).try(:[], :first)
# => :Suzuki
info = params[:user].try(:[], :info).try(:[], :full_name).try(:[], :fist)
# => nil
```

##3. fechで回避

```rb
params = { }
info = params.fetch(:user, :info)
# => :info
```
この場合、エラーは回避できますが、もし第一引数がなければ第二引数である`:info`を返すことになってしまいます。

```
params.fetch(受け取りたいキー, キーが存在しない場合の値)
```

また、第2引数はあくまで`params` のデフォルト値として扱うことになるので、ネストしたハッシュを取得するこができないので注意が必要です。

```rb

params = { user: { info: { name: { first: :Suzuki } } } }

#　ネストした値は取得できない
info = params.fetch(:user, :info)
=> {:info=>{:name=>{:first=>:Suzuki, :last=>:Itiro}}}
info = params.fetch(:user, :info, :name)
=> ArgumentError: wrong number of arguments (given 3, expected 1..2)
```
今回の目的としては不採用でしょう。

##4. digで回避

```rb
params = { }
info = params.dig(:user, :info)
# => nil
```
**キーが存在すればその値を返し、存在しなければ`nil`を返してくれます。**
これならいくらネストが増えても横長にならずに済んでみやすいですね.

```rb

params = { user: { info: { name: { first: :Suzuki } } } }

info = params.dig(:user, :info, :name, :first)
# => :Suzuki
info = params.dig(:user, :info, :full_name)
# => nil
```

参考: https://techracho.bpsinc.jp/hachi8833/2017_11_30/48336

##digを使おう

ネストした値を取得するとき、
**・ エラーを気にする必要がない。**
**・ 簡潔でワンライナーにしたときにみやすい**
という点でみると、個人的に`dig`が一番最適かなと思います。

```rb
params = { user: { info: { name: { first: :Suzuki } } } }

params[:user][:info][:name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:name] && params[:user][:info][:name][:first]
#=> :Suzuki
params[:user].try(:[], :info).try(:[], :name).try(:[], :first)
# => :Suzuki
params.dig(:user, :info, :name, :first)
# => :Suzuki
```

 `irb`とかで簡単に動作確認できるので、ぜひリファクタとかで使ってみてください！
go言語の無名関数を変数に代入しない場合

詰まったところがあったので、自分用のメモとして記録します。

# go言語の無名関数
よくある書き方が以下のとおりです。

```go
package main

import (
	"fmt"
)

func main() {
    i := 1
    f := func(){ fmt.Println(i) }
    f()
}
```

ですが、これは変数に代入しなくても、以下の記述でも動きます。

```go
package main

import (
	"fmt"
)

func main() {
    i := 1
	func(){ fmt.Println(i) }()
}
```

ポイントは、最後の`()`でこれが関数を動かすトリガーになります。
JIRAの数値フィールドにて画面表示の度にカウントアップする方法

###画面表示の度に数値フィールドのカウントをアップする
①カウントアップ対象の数値フィールド（カスタムフィールド）のIDをメモする
　（フィールド構成画面を開いた時にURLにIDが表示されている）

②フィールドの編集画面の説明欄に下記のスクリプトを記載する
  （customfield_nnnnnの「nnnnn」はカスタムフィールドID）
　（編集画面でカウント用フィールドは非表示にする記述になっています）

```groovy

<script>
  AJS.toInit(function(){
     var countField = document.getElementById('customfield_nnnnn');
     if (countField){countField.parentElement.style.display='none';
     var val = countField.value;
     if (val){
         countField.value = Number(val) +1;
         } else {
         countField.value = 1;
         }
     }
});
</script>
```

③上記フィールドを画面定義する。


*JIRA Softwareバージョンは7.12.1*
CloudPubSubでhashデータをpublishするときにハマった

Cloud Pubsubにhashでメッセージを送ったときに、全部文字列になってハマったよって話

# 結論

hashデータはto_jsonするなどしてstring化してpublishしよう

# 何が起きたか

RubyでPubsubにpublishする際、色んなパラメータを渡したいのでHashに色々突っ込んで送っていた

```ruby

Google::Cloud::Pubsub(params).topic(:hoge_topic).publish data
```

これでpubsubが普通にkickできるのだが、Cloud Functionsのattributesで受け取った際にevent['attributes']['hoge'] == Trueのような比較で想定通りに通らなくなった。

よくよくデータを見てみたところ、Ruby側でHashを送るとkey:valueのペアごとにstring化されてしまっている。

publish側

```ruby
require 'google/cloud/pubsub'

pubsub = Google::Cloud::Pubsub.new({ project_id: 'consulting-cloud-dev', credentials: 'credential.json'})
pubsub.topic(:hoge_topic).publish({ 'true': true, 'false': false, 'nil': nil, 'num': 1, 'float': 3.4 })
```

subscribe側 (python on Cloud Functions)

```python
def hello_pubsub(event, context):
    print(event['attributes'])
```

Stackdriver上のログ

```
{'false': 'false', 'float': '3.4', 'nil': '', 'num': '1', 'true': 'true'}
```

全部''で囲われていて文字列扱いになっている。うーん。

# ライブラリの実装を見る

ライブラリ側の実装を見ると、Hashの場合はそれぞれstring化されてる。
https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-pubsub/lib/google/cloud/pubsub/batch_publisher.rb#L91

コメントにはprotobuf definitionと書いてあるのでprotobufを想定しているということなのか。
なるほどー。確かにJSONとは限らないですね。

```ruby
def create_pubsub_message data, attributes
  attributes ||= {}
  if data.is_a?(::Hash) && attributes.empty?
    attributes = data
    data = nil
  end
  # Convert IO-ish objects to strings
  if data.respond_to?(:read) && data.respond_to?(:rewind)
    data.rewind
    data = data.read
  end
  # Convert data to encoded byte array to match the protobuf defn
  data_bytes = \
    String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze

  # Convert attributes to strings to match the protobuf definition
  attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]

  Google::Pubsub::V1::PubsubMessage.new data: data_bytes,
                                        attributes: attributes
end
```

地味に何故メッセージ送ってるのに、Cloud Functions側でmessageに入らずattributesに入ってくるのかもここで把握。
この関数で切り替わってたのか。

# json化すればok

to_jsonつけた

```ruby
require 'google/cloud/pubsub'

pubsub = Google::Cloud::Pubsub.new({ project_id: 'consulting-cloud-dev', credentials: 'credential.json'})
pubsub.topic(:hoge_topic).publish({ 'true': true, 'false': false, 'nil': nil, 'num': 1, 'float': 3.4 }.to_json)
```

subscribe側のログ

```
{"true":true,"false":false,"nil":null,"num":1,"float":3.4}
```

nilはnullになってるし、値も元の型になってる。

messageはstring化して送りましょう。
python3で８進数をバイナリ保存するメモ



```python:solv.py
import struct

with open('./flag_clip', 'r') as rf:
    with open('./flag_out', 'wb') as wf:
        rtxt = rf.read().replace('\n',' ')
        for t in rtxt.split():
            #print(t, '=>', int(t, 8))
            wf.write(struct.pack("B", int(t, 8)))
print('end')
```
[Xcode] ビルドや実機インストールはできるのにアーカイブできない場合の対処法

Xcodeですでにリリースしているアプリをアップデートしようとしたとき、突然アーカイブできなくなるという現象が起きて困りました。

実機やシミュレータにはインストールできるのに、アーカイブだけうまくいきません。

調べたところ、Swift Compiler の optimization level の設定を No Optimization に変えることで修正できることが判明しました。

Target → Build Settings → Swift Compiler - Code Generation → Optimization Level → Release で、No Optimization を選択します。

![Screen Shot 2019-06-06 at 13.13.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225896/988f0316-1232-d5db-c655-3619e517c45b.png)
iOS13 でアプリ毎に設定されている言語を取得する

**この内容は Beta 版で確認していますので、正式リリース時に変更されている可能性があります**

[iOS 13 Preview - Features](https://www.apple.com/ios/ios-13-preview/features/)
> **Language selection per app**
> Use third‑party apps in a different language from your system language.

- iOS13 から、アプリ毎に使用する言語を設定できるようになるようです。
    - iOS の設定アプリからアプリ名をタップすると下の方に言語を選択できる項目が追加されています。
    - 選択できる言語は PROJECT の Localizations に追加されている言語です。
- 言語毎にレスポンスを切り替える API にリクエストを投げる時に、選択されている言語を渡したい。
- アプリ毎の言語設定ってどうやって取得すればいいの？

``` swift
guard let localization = Bundle.main.preferredLocalizations.first else {
  // 取得できなかった場合の処理
  return
}
print(localization)  // "en"（英語の場合）
```
OS の言語設定の取得だったもののレスポンスがアプリ毎のものに変わっただけでした。
https://developer.apple.com/documentation/foundation/bundle/1413220-preferredlocalizations
CapistranoのデフォルトのRoleについて

Capistranoでデフォルトで用意されているRoleは 3つ。
ただ、それぞれのRoleが何をするものなのかということの記載が全然ないのでメモ。

| Role | 役割 | 例 |
|:--:|:--:|:--:|
| app | Webサーバを立ち上げるかどうか | 例えばAPIだけ用意するとき |
| web | RailsでWebでHTMLを公開したいとき（Asset Compileが走る） | RailsでViewを配信。Webpackerの利用など |
| db | migrationを実行する | DBを利用する時。複数指定せず一つだけ設定すると良いと思われる |

これらを複数組み合わせて設定します。

何を設定しても `bundle install` は走ります。
プロジェクトマネジメントを勉強したついでにWBSについてまとめてみた①

#WBSってなんだっけ？
ちょうどここ1年くらいでプロジェクトマネジメント（PMBOKとかISO21500とか）を勉強する機会があったので、ちょっとずつまとめてみようと思います。

WBSとは__プロジェクト活動を分割し作業ができるレベルまで分割したもの__になります。WBSは開発を行うときにやるべきことを洗い出すのに有用な手法だと思います。ただ、よくよく見ると意外とざっくり作られていることが多く、そのことが影響してなにをやるべきかが明確になっていないことが多々あります。
終了条件が明確でなければ作業してもそこで完了になるか不明瞭ですし、前作業が何かわからなければなんとなくで作業開始することになりますよね。
（どの状態になったら作業を開始して、どこまでやったら完了なのかを明確にできるレベルまで落とし込むのがコツなのかな、と最近は思っています。）


- よくある間違い例
 - そもそも作業ができるレベルまでBreakdownできていない
 - WPの終了条件が明瞭でない
 - WPの前作業が明確でない　　等々...



#WBSに必要な要素
ではWBSに必要な要素は何になるかというと、まずはスコープの特定、そして『作業できるレベルまで分割する」です。その上で以下の項目が記載されているのが望ましいのではないでしょうか。

- WBSに必要な要素
 - 前作業がなにか
 - WPの作業内容
 - WPの終了条件
 - WPの工数
 - WPのoutput（成果物など）

ちなみに様々なところでWBSにガントチャートをくっつけて、『WBS』とまとめていることがあります。（便利ですしね！）
一応違いを理解して使い分けた方が良いのかなーなって思ってます。


#参照
https://www.itbook.info/web/%E7%9B%AE%E6%8C%87%E3%81%9Bpmp/5-3-wbs%E4%BD%9C%E6%88%90
https://www.japan-project-solutions.com/iso21500
配列内の近似値の取得

-配列と配列内オブジェクト両方。
-indexのみ欲しい場合はindexを返すよう変更する。
demo https://codepen.io/gnjo/pen/oRReQN?editors=1010

```js
let fn={}
fn.near=(_v,_ary,_key)=>{
 let f=Math.abs,v=_v,ary=_ary,key=_key
 ,index=0
 ;
 if(!key)ary.map((d,i,a)=>{index=(f(v-a[index])<f(v-d))?index:i})
 if(key)ary.map((d,i,a)=>{index=(f(v-a[index][key])<f(v-d[key]))?index:i})
 ;
 return ary[index]/////
}
;
let a=[1,2,3,4,5,10,11,12,130]
let aa=fn.near(13,a)
console.log(aa)//12
;
let b=[{v:1},{v:2},{v:3},{v:4},{v:10},{v:11},{v:12},{v:130}]
let bb=fn.near(13,b,'v')
console.log(JSON.stringify(bb,null,'  '))//{v:12}
```
iTerm2のアップデート時にzshの動作が遅くなった時の解決策

## 環境
iTerm2 3.2.9
macOS Mojave Version 10.14

## 対処法
iTerm2のアップデートしたら、zshの起動や動作が遅くなった。
原因はiTerm2 shell integrationが古かったため。
再インストールすると直った。

```
$ curl -L https://iterm2.com/shell_integration/zsh -o ~/.iterm2_shell_integration.zsh
$ source ~/.iterm2_shell_integration.zsh
```
価格登録すると、その数字に「桁区切り」と「円」を追加するプログラムの実装方法

#1.桁区切り」と「円」を追加するプログラムの実装の手順

ヘルパーメソッドconverting_to_jpyと使って、価格の数字に「桁区切り」と「円」を追加するプログラムを実装する。そして、その表示をビューに表示させる方法について書きます。

実装の手順としては、
1.product_helper.rbにヘルパーメソッドconverting_to_jpyを定義すること。
2.次にindex.html.erbを編集すること。

#2.product_helper.rbにヘルパーメソッドconverting_to_jpyを定義する
```ruby

app/helpers/products_helper.rb

module ProductsHelper
  def converting_to_jpy(price)
    "#{price.to_s(:delimited, delimiter: ',')}円"
  end
end
```

#3.index.html.erbを編集する
```html

<p id="notice"><%= notice %></p>

<h1>Products</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Price</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.name %></td>
        <td><%= converting_to_jpy(product.price) %></td>
        <td><%= link_to 'Show', product %></td>
        <td><%= link_to 'Edit', edit_product_path(product) %></td>
        <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Product', new_product_path %>

　　　　　　　　　　　　　　　　　　　　
```
以上にような、実装を行うことで、価格の数字に「桁区切り」と「円」を追加するプログラムの実装が完了する。例えば「1000」という数値が引数で与えらたら「1,000円」といったようにビューに表示される。
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　以上
githubショートカット

#目的
githubショートカットについて記載してます。

#g + c
Codeページ（リポジトリトップ）に遷移します。
リポジトリトップに戻れます。

#g + p
Pull Requestsページ（Pull Request 一覧）に遷移します。

#g + i
issueに遷移します。

#ctrl + shift + p 
プレビューとコメントを切り替えます。

#l
ラベルが選択できます。

#m
マイルストーンが選択できます。
PHP で .ipa ファイルからプロビジョニングプロファイルの情報を取り出す

iOS のエンプラ配布でやらかしたのでメモ。

iOS アプリの配布には、大きく２種類分けて、通常のストア配布用の [Apple Developer Program](https://developer.apple.com/jp/programs/) と、特定の企業向けに配布する [Apple Developer Enterprise Program](https://developer.apple.com/jp/programs/enterprise/) があります。
今回は後者の通称「エンプラ配布」でのお話。

一般的に iOS アプリの配布には、配布用証明書とプロビジョニングプロファイルが必須ですが、このプロファイルには有効期限があり作成日から365日という制限があります。通常はアプリビルド時にチェックが入り、期限が切れたプロファイルでのビルドが出来ない、というだけなのですが、エンプラ配布の場合はそれに加えて、**有効期限を１秒でも過ぎるとアプリが起動できなくなる**、という罠があります。

ええ、やらかしました。本日。

小さなシステムだとアップデートしてもらえば良いのですが、全国支店に何百台と配布しているシステムではそう簡単な話ではなく、相当な人的コストをかけて一台一台アップデートしなければなりません。
そんなわけで朝から関係各所からお叱りを受け、とてもテンション下がってますが、繰り返さないためにもここにメモを残します。(こういったことを避けるために MDM やデプロイサービスというものが存在することは重々承知ですが、大人の事情で導入できていません。In-House だと TestFlight も使えないし...)

そもそも MDM なしでインストール済みのアプリ内プロファイルの証明書をリモートから読み取る手段はありません（私の知る限り）。
そこで、弊社では配布する ipa ファイルを S3 上で配布バージョンごとに残しておき、現場で動いているアプリバージョンと比較して、ざっくりと1年間という期限を管理していました。

よく勘違いするのですが、この有効期限は、アプリをビルドしてから365日、ではなく、また、配布用証明書の有効期限でもなく、プロビジョニングプロファイルを作成してから365日です。「こまめにアプリをアップデートしているから大丈夫」「配布用証明書の有効期限は2年くらい先だからまだまだ大丈夫」とった慢心が今回のような事故を招きます。（自戒）

で、このプロビジョニングプロファイルですが、ipa ファイルさえ残っていれば、どのプロファイルを使用してビルドしたか、や、有効期限そのものを直接参照することができます。今までは S3 上の ipa ファイルを落としてきて zip で解凍して、embedded.mobileprovision ファイルを開いて、、、ということを手作業でやってきましたが、それを PHP で実装したら10行程度だったので公開します。

```php
$appName = 'AppName'; // アプリ名称
$profilePath = "zip://{$appName}.ipa#Payload/{$appName}.app/embedded.mobileprovision";
$ipa = file_get_contents($profilePath);
// 正規表現で plist タグ内の XML を取得
if (preg_match('/<plist version="1.0">([\s\S]+)\<\/plist>/', $ipa, $matches)) {
    $plist = simplexml_load_string($matches[1]);
    $currentKey = null;
    foreach ($plist as $key => $value) {
        if ((string)$key === 'key') {
            $currentKey = trim((string)$value);
        } else {
            $params->$currentKey = trim((string)$value);
        }
    }
}

echo $params->ExpirationDate;
// 2020-06-05T03:47:37Z
```
ipa ファイルは zip アーカイブファイルなので、 PHP では、zip ストリームラッパー (zip://ipaファイル#圧縮内パス) で透過的にプロビジョニングプロファイルの中身を直接参照しています。

プロビジョニングプロファイルの情報は下記のような構成になっています。

```xml
〜バイナリ〜
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>AppIDName</key>
	<string>アプリ名</string>
〜省略〜	
	<key>CreationDate</key>
	<date>2019-06-05T03:47:37Z</date> ← プロファイル作成日
〜省略〜	
	<key>ExpirationDate</key>
	<date>2020-06-05T03:47:37Z</date> ← プロファイル有効期限
	<key>Name</key>
	<string>プロビジョニングプロファイル名</string>
〜省略〜	
</dict>
</plist>
〜バイナリ〜
```

この通り、XMLの前後にバイナリが記載されており、XML も Mac や iOS でお馴染みの plist 形式になるので、直接読み取ることができないので、雑な正規表現で取ってきて key と value を適当にパースしています。

同様に Info.plist の情報も読み取れるので、バージョン情報と共に、定期的にチェックして有効期限が近づくとアラートを出すとか出来そうですね。

やってみれば何てことはないしPHPじゃなくても出来ますが、これで同様の事故の防止に役立てば幸いです！
SFTPサーバの設定

- 専用のユーザを作成して、SFTP 接続のみ利用可能で、SSH 接続を許可しない。
- アップロード先のディレクトリだけアクセス可能にしたい。
- 対象クライアントは多数想定

`SFTP専用のユーザを作成してパスワードを設定する`

$ sudo useradd sftp-user
$ sudo passwd sftp-user 

`SFTP専用ユーザのホームディレクトリを /（rootディレクトリ）に設定する
ChrootDirectory を使うと見かけ上ホームディレクトリは / になるため。`

$ sudo usermod -d / sftp-user 

`実際にファイルを読み書きするディレクトリを作成する`

$ sudo chown root:root /home/
$ sudo mkdir /home/sftp-user
$ sudo chown root:root /home/sftp-user
$ sudo chmod 755 /home/sftp-user

`SFTP専用ユーザーのグループを作成する`

$ sudo groupadd sftponly

`SFTP専用ユーザーのグループを設定を変更する`

$ id sftp-user 
uid=1001(sftp-user) gid=1001(sftp-user) groups=1001(sftp-user)
$ sudo usermod -G sftponly sftp-user
$ id sftp-user 
uid=1001(sftp-user) gid=1001(sftp-user) groups=1001(sftp-user),1002(sftponly)
sshd設定、internal-sftpを使用する

$ sudo vim /etc/ssh/sshd_config 

`/usr/lib/openssh/sftp-serverをコメントアウトし、 internal-sftp を追加する`

-#Subsystem sftp /usr/lib/openssh/sftp-server
Subsystem sftp internal-sftp

`SFTP専用ユーザが読み書きするディレクトリを作成する`
下記の例では、接続すると test ディレクトリのみが表示されるようになる。

$ cd /home/sftp-user
$ sudo mkdir test
$ sudo chown sftp-user:sftp-user test

`SFTP専用ユーザをchrootに閉じ込める`

$ sudo vim /etc/ssh/sshd_config 
下記の設定を追加する。

Match group sftponly
  ChrootDirectory /home/%u
  X11Forwarding no
  AllowTcpForwarding no
  ForceCommand internal-sftp
  AuthorizedKeysFile /home/%u/.ssh/authorized_keys

`sshdを再起動する`

$ sudo service sshd restart

`sftpログイン確認`
sftp -i ~/.ssh/xxxx.pem -oStrictHostKeyChecking=no ログインユーザ@IPアドレス

`ユーザを追加する場合`
SFTP専用グループの作成は不要で、ユーザをグループに追加する


- 参考文献
- 
SFTPサーバを用意する - Qiita (usutaniさんの投稿)
https://qiita.com/usutani/items/fdabef73b5dff60c95c6
JavaScript, Async, Await 筆記

## `async`

主要用在 **宣告**

``` js
async function someFunction() {}
```

或是

``` js
someClass.someFunction(async () => {});
```

## `await`

主要用在 **呼叫** async method 或是 Promise 的時候，掛在前面關鍵字

``` js
const result = await someFunction();
```

## Error handling

在執行有掛 `await` 的非同步方法外面包 try-catch

``` js
try {
  const result = await someFunction();
  console.log(error.result);
} catch (error) {
  console.log(error.message);
}
```