OmniAICreator/Qiita-1.07M huggingface dataset:
Antilatencyのセットアップ

前回に引き続いてantilatencyの話ですが、
はじめに↓を読んでいただけると幸いです。

○[antilatencyというトラッカーについて](https://qiita.com/SotaroAbe/items/ab1bd54a62c5f7384ece)

今回は、antilatencyの基本的なセットアップについてまとめました。

---
・[公式セットアップ手順](https://antilatency.com/getting-started/latest/prepare)
手順は↑の通りです。ほぼ引用になりますが、個人的につまづいた所など追記しながら解説していきます。

##環境の準備
####□用意するもの
・1…ALT
・1…Wired USB Socket
・トラッキングエリアキット
　　○4…IRマーカー付きバー
　　○4…接続用バー
　　○1…電源バー
　　○7…バー接続用コネクタ
　　○28…フロアマット
　　○1…電源アダプタ

以上が、最低限必要なものになります。

####1.トラッキングエリア
IRバーを床に敷いて、マーカーのレイアウトを作ります。
正しい向きにする必要があるので、二つのマーカーが近い距離で並んでいる箇所を判断しながら、以下の様に並べます。
![スクリーンショット 2021-01-26 19.07.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939279/6877f373-3258-5feb-996e-88db1d8dfd80.png)
・4…IRマーカー付きバー
・1…電源バー
・1…電源アダプタ
・4(上図の場合)…バー接続用コネクタ
・16（上図の場合）…フロアマット

標準のエリア面積は2.4×2.4mになりますが、カスタムで面積を広げる事も可能です。その場合の配置も、[公式ページ](https://antilatency.com/getting-started/latest/area#content)にて掲載されています。

##ソフトウェア
公式より[サービスアプリ](https://antilatency.com/getting-started/latest/soft#content)がリリースされていますので、ダウンロードします。
インストール時に、使用中のマシンが開発者モードになっている必要があります。(自分の場合はそうでした) 
####□ENVIROMENTS
![スクリーンショット 2021-01-26 19.26.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939279/e72b63fa-60c2-fabc-0f91-3c3421f72045.png)
マーカーのレイアウトを設定します。設置したマーカーと同じ配置を選びましょう。
####□PLACEMENTS
![スクリーンショット 2021-01-26 19.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939279/1744cc68-0d65-ab81-bf63-9918d61aafa1.png)
HMDにALTを装着する際の位置を決めます。
特にヘッドセットを使う必要がなければ、ここは飛ばして大丈夫です。
####□DEVICE NETWORK
![dn-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939279/2c30f824-f02a-19d5-8da7-b3464de02b75.png)
この項目では、 ALTデバイスのプロパティを読み書きすることで、接続するデバイスを制限したり、HMD無線ソケットの無線チャンネルを設定します。デバイスに名前を付けて、アプリケーションに特定のデバイスを認識させることも可能です。

---
基本的な準備は以上になりますが、ゲームやエンジン内で動作させるには各々設定しなくてはなりません。 
今回の動作検証はUnrealEngineで行ったので、次はその時の事についてまとめたいと思います。
docker版Giteaのアップデート

https://qiita.com/k8uwall/items/63c6424a976012122d18

あれから2年、そろそろアップデートしてみる。

```yml:docker-compose.yml
version: '3'
services:
  web:
    #image: gitea/gitea:1.7
    image: gitea/gitea:1.13.1
    volumes:
      - ./gitea-data:/data
    ports:
      - "3000:3000"
      - "10022:10022"
    environment:
      - TZ=Japan
      - SSH_PORT=10022
    restart: always
```

START_SSH_SERVERの行をコメントアウトする

```bash
$ vi gitea-data/gitea/conf/app.ini
;START_SSH_SERVER = true
```

```bash
$ docker-compose pull
$ docker-compose up
```

クライアント側から参照するサーバー側のkeyが変わるらしいので
クライアント側のknowns_hosts行を削除する。

```bash
$ vi ~/.ssh/known_hosts
[192.168.1.100]:10022 ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx　←削除
```


### 他

ログに以下の行がssh接続するたびにエラーが記録されてしまうが、動きは問題ないらしい。
[ssh logs about missing host certificates · Issue \#13724 · go\-gitea/gitea](https://github.com/go-gitea/gitea/issues/13724)

```
web_1  | Could not load host certificate "/data/ssh/ssh_host_ed25519_cert": No such file or directory
web_1  | Could not load host certificate "/data/ssh/ssh_host_rsa_cert": No such file or directory
web_1  | Could not load host certificate "/data/ssh/ssh_host_ecdsa_cert": No such file or directory
web_1  | Could not load host certificate "/data/ssh/ssh_host_dsa_cert": No such file or directory
```
RNA-seq解析 - R言語でvolcano plotを描く

volcano plotとは、RNA-seq解析をした際に、発現比較解析の結果を表すグラフです。
横軸に発現比(log2)、縦軸にはp値(-log10)をとります。

R言語でvolcano plotを描く機会があったので、備忘録としてまとめたいと思います。

(2021年5月9日追記) ggplot2で書く方法はこちらから

https://qiita.com/shibanosuke/items/8d36bcd984f341059fbe


本稿では、発現比較解析を行い発現比とp値はすでに算出している前提で、グラフを作ります。
なお、R studio (ver. 1.3.1093)、R (ver. 4.0.3)を用いました。

使用したスクリプトは以下の通りとなります。発現比はfold changeとしています。

```R
# R. User (shibanosuke) 27 January, 2021
#This script is for making volcano plot

#Import dataset
library(readxl)
RNA-seqdata <- read_excel("RNA-seqdata.xlsx")
View(RNA-seqdata)  

#Fold change(log2)の代入
m <- RNA-seqdata$Fold.Change

#p-valueの代入
p <- RNA-seqdata$p-value

#p-valueを-log10で対数変換し、代入
logp <- -log10(p)

#p-valueの閾値設定の数値を代入
pval <- -log10(0.05)

#volcano plotの作成
#ポイントの形は20(黒丸)、大きさを0.5とした
#発現が減少したものを青、増加したものを赤とした
#閾値はp値が0.05未満、fold change(log2)が1以上 or -1以下
plot(m, logp, pch = 20, cex = 0.5, main = 'vorcano plot', xlab = 'log2(Fold Change)', ylab = '-log10(p-value)', col = ifelse(logp <= pval,'black',ifelse(m >= 1,'red',ifelse(m <= -1,'blue','black'))))

#境界部分に、darkgreenの点線を引いた
#p値が0.05、fold change(log2)が1 or -1
abline(v = 1, col= 'darkgreen', lty=2)
abline(v = -1, col= 'darkgreen', lty=2)
abline(h = pval, col= 'darkgreen', lty=2)
```

excelファイルからのimportは、R studioのimport機能を用いました。
excelファイルをimport後、fold change値とp値をそれぞれ代入し、p値は対数変換(-log10)します。

volcano plotの作図はplot関数を用い、閾値はp値が0.05未満、fold change(log2)が1以上もしくは-1以下としました。また、発現量が増加したものは赤色、減少したものは青色、それ以外は黒色でプロットすることで、分かりやすくしました。
閾値の境界部分を分かりやすいように、abline関数で点線を引き、volcano plot完成です。

最後に、今回使用したスクリプトで作図したvolcano plotを紹介します。
使用データは私が取ったデータを一部改変したものとなります。
volcano plotを作図することで、発現変動遺伝子の分布が分かりやすく可視化されたと思います。

![Rplot.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1060100/a0d021b1-21d4-74ab-f0e0-e9288c62cede.jpeg)
【Java(Spring Boot)】@RequestBodyとは

#プログラミング勉強日記
2021年1月27日
Spring Bootを使っていると以下のようなエラーが出た。調べてみると、コントローラメソッドの引数の中で`@Requestbody`が2回使用しているのが原因だった。メソッドごとに1つの`@RequestBody`しか使えないのでエラーが出るみたいだった。

```:エラー内容
I/O error while reading input message; nested exception is java.io.IOException: Stream closed
```
　正直、コードを書いていてSpring Bootを使ってpostの処理をする場合には`@RequestBody`を付けるみたいな感じでちゃんと理解してなかった。なので、今日は`@RequestBody`について理解する。

#`@RequestBody`とは
　ユーザから投稿(POST)されたデータを受け取るSpring Bootアプリが、投稿を受け取る作業のために`@RequestBody`をつける。なので、`@RequestBody`は取扱説明書みたいなもので、投稿された内容を受け取る際の取り扱い方法を指定している。`@RequestBody`を付けることで、json形式で受け取る。
　`@RequestBody`のついた内容を受け取っているのはSpringbootAplication本体である。


#参考文献
[入力メッセージの読み取り中にI / Oエラーが発生しました。ネストされた例外はjava.io.IOExceptionです:ストリームが閉じられました](https://python5.com/q/yjhybtqp)
URI(URL)でユーザ名とパスワードを指定してログインしながらアクセス

# 問題
画像のように，ログインが必要なサイトにアクセスしたいが，プログラム中などの理由でGUIは使えない．
![スクリーンショット 2021-01-27 22.12.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455102/73ea128d-7b18-3ea1-9684-8565b27e78b0.png)

# 解決方法
URI(URL)に，ユーザ名とパスワードを仕込むことができます．
今回は，
`http://192.168.10.1`に
ユーザ名：user
パスワード:pass
でアクセスしたいとします．

その場合，
`http://user:pass@192.168.10.1`と入力することで，アクセスができます．

僕の場合は，Axisのカメラにpythonからアクセスする際に使用しました．
Reactエラー備忘録2 : Error: Reducer "products" returned undefined during initialization.

# 概要

React アプリを開発する上で発生したエラーの原因と対策を忘れないためのメモです。

### 状況

某商品紹介用の Web アプリの商品追加画面制作中に発生。

### エラー

```
> Error: Reducer "products" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.
```

### コード

```javascript
export const ProductsReducer = (state = initialState.products, action) => {
  switch (action.type) {
  }
};
```

### 原因

reducer の switch 文の中に default のケースを設定していなかったこと

### 対策

switch 文に default のケースを追加することで解決

```javascript
export const ProductsReducer = (state = initialState.products, action) => {
  switch (action.type) {
    default:
      return state;
  }
};
```
Windows10(WSL2)でRails/Dockerの開発環境を構築したい

### まず、Windows10でWSL2を使えるようにする。

[公式ドキュメント通りに進める](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)

私はUbuntuを入れました。(Ubuntu 20.04 LTSで大丈夫です。)

### Docker EngineとDocker-ComposeをUbuntuにインストールする。

Docker Engineを先に入れてから、Docker-composeを入れる。
> [理由](https://docs.docker.com/compose/install/)：On Linux systems, first install the Docker Engine for your OS as described on the Get Docker page, then come back here for instructions on installing Compose on Linux systems.

### [開発ディレクトリについて（重要）](https://qiita.com/amenoyoya/items/ca9210593395dbfc8531#%E9%96%8B%E7%99%BA%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E9%87%8D%E8%A6%81)
> WSL2 環境において開発ディレクトリをどこに置くかは重要です。

> 開発ディレクトリを Windowsファイルシステム側（Linuxパス: /mnt/c/...）に置いた場合、ファイル IO が異常に遅く、一部 Docker 環境ではネットワーク通信に不具合が発生するなどの問題が起こります。
> そのため、基本的には \\wsl$\Ubuntu-20.04\home\<ユーザ名>（Linuxパス: /home/<ユーザ名>）など、Linuxファイルシステム側に開発ディレクトリを置く必要があります。
開発ディレクトリがLinuxファイルシステム側に置いてあれば、Dockerプロジェクトも安定・軽快に動かすことができます。（少なくとも今のところは）

#### [Docker EngineをUbuntuに入れる](https://docs.docker.com/engine/install/ubuntu/)

#### エラーが出た場合

```bash
~$ sudo docker run hello-world
docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.
See 'docker run --help'.
```

私は`sudo service docker start`を使うと動きました。([参考](https://qiita.com/neight0903/items/ef0e2170b712b194c3fc))

#### [Docker-ComposeをUbuntuに入れる](https://docs.docker.com/compose/install/)

### Docker-composeを使用して、Railsをインストールする。

#### [Docker社のクイックスタートを参考にして入れる](https://docs.docker.com/compose/rails/)

#### エラーが出た場合

```bash
W: Failed to fetch http://deb.debian.org/debian/dists/buster/InRelease  Temporary failure resolving 'deb.debian.org'
W: Failed to fetch http://security.debian.org/debian-security/dists/buster/updates/InRelease  Temporary failure resolving 'security.debian.org'
W: Failed to fetch http://deb.debian.org/debian/dists/buster-updates/InRelease  Temporary failure resolving 'deb.debian.org'
W: Some index files failed to download. They have been ignored, or old ones used instead.
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package nodejs
E: Unable to locate package postgresql-client
ERROR: Service 'web' failed to build : The command '/bin/sh -c apt-get update -qq && apt-get install -y nodejs postgresql-client' returned a non-zero code: 100
```

どうやらコンテナのDNS設定によるものらしい。

私は、[この通りに](https://snowsystem.net/container/docker/apt-get-error/#toc_id_1)/etc/wsl.confと/etc/resolve.confを編集したら動きました！！
【備忘録】ローカル環境でのWordPressスタートアップ

#はじめに
ブログ更新が遅くなってしまいました汗
複数のことを同時に行うことができるように工夫して頑張ります！
今回のブログからWordPressにフォーカスをして記事を書いていきたいと思います。
よろしくお願いします。


#開発環境
今回はMAMPというローカル環境で開発を行えるソフトを使用します。
詳しくは [こちら](https://techacademy.jp/magazine/5102)の記事を参照ください。


#WordPressとは
WordPress（ワードプレス）とは世界で一番使われているCMS（コンテンツマネジメントシステム）のことを言います。
CMSとはシンプルにいうとブログシステムです。ブログサイトを運営するのに特化したwebサイト上でコンテンツの管理ができるのでとても簡単にブログサイトを運営できます。


#WordPressの始め方
1.WordPressは簡単に始められます。
 [こちら](https://ja.wordpress.org/download/)のWordPress org（ワードプレスオルグ）のダウンロードページから必要なデータをダウンロードします。
2.ダウンロードしたデータはFinderのアプリケーション>MAPM>htdocsの中にコピーします。
3.コピーしたファイルの名前はここではblogとします。
4.MAMPを起動してstart serverをクリック、そうすると自動でMAMPのページがブラウザで開かれます。
5.自動で開かれたページのURLをhttp://localhost:8888/blog/と変更しクリック
6.WordPressの設定画面に遷移されるので最初のページは「さあ、始めましょう！」をクリックします。
7.データベースの設定画面に遷移されます。もう一度MAMPの[こちら](http://localhost:8888/MAMP/?language=English)を開きTOOLSクリックし次にPHPMYADMINをクリックします。
8.左のサイドバーの上にある「New」をクリックします。
9.データベース名を「wordpress」としUTF8_general_ciを選択、「create」をクリックします。
10.「さあ、始めましょう！」をクリックしたページに戻りデータベース名を「wordpress」、ユーザー名「root」、パスワード「root」、データベースのホスト名「localhost」、テーブル接頭辞「wp_」とし、「送信」をクリックします。
11.問題がなければ無事にインストールが完了しましたと表示が出ます。
12.「インストール実行」をクリックしたあと、「ようこそ」とWordPressの設定画面が出てきます。
13.ここの設定はあとで変更ができるので、サイトのタイトルを「テスト」、ユーザー名を「admin」、パスワードとメールアドレスはそれぞれご用意お願いします。「検索エンジンの表示」はローカル環境なのでチェックしなくても大丈夫です。
14.「インストールを実行」をクリックし、画面に従ってログインします。
これでローカル環境でWordPressが使えるようになりました！


#最後に
今回はスタートアップまでのご紹介となりました。
次回はWordPressの子テーマに関する記事をアップしたいと思います。
よろしくお願いします！
【随時更新】中国語のプログラミング・IT用語は案外わかりやすいかも

# 前説
普段は外来語のまま覚えてしまっている、プログラミング・IT用語に中国語で接してみようという趣旨です。 
中国語を通してそれらの用語に接することで、その用語の本当の意味が分かるのではないか、と思います。 
「なるほど！言われてみればそうか！」という秀逸な単語も多いです。 
モダンな技術をキャッチアップする際、中国語を通すことで意味が理解しやすいといったこともあるのではないでしょうか。
また、これらの用語に疎い層との意思疎通を図る際のヒントになることもあるのではないでしょうか。

# インフラ

| 日本語 | 英語 | 中国語(簡体字) | 所感 |
|:-:|:-:|:-:|:--|
| インフラ  |  infrastructure | 基础  | 言われてみれば基礎  |
|  サーバー |  server |  服务器  | サービスデスクを服務台と記してある様子はホテルでよく見る。サービス=服務か  |
|クライアント|client|客户端| "客"が含まれているのでクライアントを指すようになった？ |
| 仮想 | virtual  |  虚拟的  | 虚ろの擬似的な、みたいな意味なのか  |
|  ネットワーク | network | 网络 | 網？と連絡の絡 → 網同士の繋がり的な意味なのか |
|  VPN | Virtual Private Network | 虚拟专用网络  | 专用=専用なので、仮想専用ネットワークと言われると覚えやすい  |
| セキュリティ  |  security | 安全  | ネットワークのセキュリティ→ネットワークの安全と置き換えても意味が通るし、疎い層にも直感的にわかりやすそう  |
|データベース| database | 数据库 | 数を据える庫 |
|プロトコル | protocol | 通信规约 | 文字通り |
|クラウド | cloud | 云 | 英語の直訳 |
|クラウドコンピューティング | cloud computing| 云计算 | そのまんま |

# ソフトウェア
| 日本語 | 英語 | 中国語(簡体字) | 所感 |
|:-:|:-:|:-:|:--|
|プログラム| program | 程序 | 段取りみたいに見えなくもない字面 |
| オブジェクト指向プログラミング  |object-oriented programming | 面向对象的程序设计  |  対象(オブジェクト)に向き合ったプログラムの設計 | 
| ポリモーフィズム  | Polymorphism  | 多态 | 多型という書き方も見た。読んで字の如し  |
|  カプセル化 | Encapsulation  | 封装 | プライベート変数、メソッドに封をするような印象  |
|  クラス | class  |  类 | 类=たぐい、という意味があるそうで、直訳的  |
| インターフェイス | interface | 接口 | 接合部を指す概念。これは漢語のほうが馴染みやすいか。 | 
| アクセストークン| access token | 访问令牌| 持っている者のみアクセスが許される牌(≒札)。札なのでリクエスト元が当然持ってくる。ちなみにバイドゥで画像検索すると、お札の画像も出てくる。 | 

# 出典
- 牛津英汉汉英词典 / Oxford Chinese Dictionary
- [Weblio 日中・中日辞典](https://cjjc.weblio.jp)
- Wikipedia

等
Vue.js で 親コンポーネントと同じ高さに設定する方法を考えた

# 背景

Vue.js で親コンポーネントと同じ高さにする。

具体的には、`height:auto`（コンテンツの内容に応じて、heightが変わる。）のコンポーネントに対して、
ある特定条件を満たしている場合、透明色の色をかぶせる。と言うことが必要となりました。

`height: 100%` でも指定しておけば、うまくいくだろう。と思って試してみましたが、
`height:100%` が適用されるのは、親コンポーネントのheightが指定されている場合のみらしく、今回適用したい `height:auto` ` の場合には、適用できないとのこと。異なる方法で対応を検討する必要が生じました。

# 結論

結論として、以下の方法で対応しました。

```html:template
<template>
  <div>
    <div :style="{height:fullHeight}" /> // 高さを指定する。
 </div>
</template>
```

```ts:script(typescript)

import {Vue, Component, Prop} from 'vue-property-decorator';

@Component({})
export default class Sample extends Vue {
  fullHeight:string;

  mounted() {
    this.fullHeight = this.$el.clientHeight + 'px'; // DOMから高さを取得する。
  }
}
```

# 説明
## 概要
Vue.js のAPIでElementを取得して、高さを取得しています。
シンプルですが、この方法を取ることで、Elementが持つ他のプロパティを取得することも可能です。

Vue.js API
- https://jp.vuejs.org/v2/api/index.html#el
Element のプロパティ
- https://developer.mozilla.org/ja/docs/Web/API/Element

## 注意事項
### mounted で取得する
`created` や `computed` を使用すると、 Element(DOM) を生成する前に、Element要素にアクセスすることになるので、エラーを吐いてしまいます。

詳細は、以下サイトにて
- https://jp.vuejs.org/v2/guide/instance.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%A9%E3%83%A0

時間があれば、調べた経緯とかも整理して、編集したいと思います。
SQLAlchemy クエリ確認方法

# SQLAlchemyで実行されるクエリをprint出力する方法を記述します。

<details><summary>DB接続用ファイル(必要な方はどうぞ)</summary><div>

```qiita:setting.py
import os
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from dotenv import load_dotenv
load_dotenv()

# mysqlのDBの設定
db_user = os.getenv('DB_USERNAME')
db_pass = os.getenv('DB_PASSWORD')
db_port = 3306
db_hostname = os.getenv('IP_ADDRESS')
db_name = os.getenv('DB_NAME')
print('DB_USERNAME:{}, IP_ADDRESS:{},DB_NAME:{}'.format(
    db_user, db_hostname, db_name
))

ENGINE = create_engine(
    sqlalchemy.engine.url.URL(
        drivername="mysql+pymysql",
        username=db_user,
        password=db_pass,
        host=db_hostname,
        port=db_port,
        database=db_name,
        # query=query_string
    ),
)

# Sessionの作成
session = scoped_session(
    # ORM実行時の設定。自動コミットするか、自動反映するなど。
    sessionmaker(autocommit=False,
                 autoflush=False,
                 bind=ENGINE)
)
print('sesssion')
# modelで使用する
Base = declarative_base()
Base.query = session.query_property()
```
</div></details>


```qiita:main.py
from .setting import session # <-DB接続用のセッションを読み込んでます（不要であれば削除して構いません）
from sqlalchemy.dialects import mysql

def main():
    query = session.query(SomethingTable)
    print(query.statement.compile(
            dialect=mysql.dialect(),
            compile_kwargs={"literal_binds": True}))
```

```qiita:実行結果
SELECT something.id, something.name
FROM something
```


参考ページ
[SQLAlchemyが生成するクエリを確認する](https://techblog.recochoku.jp/2423)
AWSにアップロードできるサーバー証明書の数が上限に達したときの対処

# 現象

AWSにサーバー証明書をアップロードしようとしたとき、以下のようなエラーメッセージが表示されました。

```
cannot exceed quota for servercertificatesPerAccount: 20
```

AWSにアップロードできるサーバー証明書は初期ではアカウントあたり20までに制限されており、その制限を超えることはできません。

# 対処方法

対処方法としては以下の2つが挙げられます。
しかし、根本的にはAWS Certificate Managerを活用し、証明書をアップロードしないほうがよいです。

1. [不要なサーバー証明書の削除](#不要なサーバー証明書の削除)
2. [サーバー証明書の制限（クォータと呼ばれる）を引き上げる](#サーバー証明書の制限を引き上げる)


## 不要なサーバー証明書の削除

AWS-CLIを使う場合には以下のように実行して不要なサーバー証明書を削除します。
AWS-CLIは、後述のCloudShellを使うことでウェブブラウザだけでコマンドを発行できます。

```
aws iam delete-server-certificate --server-certificate-name <サーバー証明書の名称>
```

上のコマンドで必要な証明書の名称はアップロードしている証明書の一覧から探すと良いでしょう。  
以下のコマンドで表示されることができます。

```
aws iam list-server-certificates
```

参照：[IAM でのサーバー証明書の管理 - AWS Identity and Access Management](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_server-certs.html)


### エラーが発生するとき

以前コマンドを実行したとき、以下のようなエラーが発生しました。

```
An error occurred (DeleteConflict) occurred when calling the DeleteServerCertificate operation: ...＜このあとはメモしそこねた＞...
```

どうやら、利用中の証明書を削除しようとしたときに発生するようです。  
証明書を利用しているサービスを確認しましょう。

### CloudShellを使ってWebブラウザだけでAWS-CLIを使う

AWS-CLIをローカルPC等にインストールせずともWebブラウザがあればコマンドを実行できる[AWS CloudShell](https://aws.amazon.com/jp/blogs/news/aws-cloudshell-command-line-access-to-aws-resources/)が便利でした。  
2020年12月にリリースされたサービスです。

AWSコンソールからCloudShellを選び、環境ができたらすぐに上記のコマンドが使えます。

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

- [AWS CloudShell とは?](https://docs.aws.amazon.com/ja_jp/cloudshell/latest/userguide/welcome.html)
- [AWS CloudShell – AWS リソースへのコマンドラインアクセス | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/aws-cloudshell-command-line-access-to-aws-resources/)
- [待望の新サービス AWS CloudShell がリリースされました！ #reinvent | Developers.IO](https://dev.classmethod.jp/articles/new-service-aws-cloudshell-reinvent-2020/)

## サーバー証明書の制限を引き上げる

方法は下記を参照してください。
すぐには適用されないようなので、すぐに対応が必要な場合にはだめです。

- [SSL/TLS 証明書のクォータの引き上げ - Amazon CloudFront](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/increasing-the-limit-for-ssl-tls-certificates.html#certificates-to-upload-into-iam)
TypeScript + Seleniumのヘッドレスモードで実行したら指定したディレクトリにファイルがダウンロードされない場合の対処法

こんにちは。
2020年と2021年は例年と違い新しい生活様式を求められたり、リモートワークが当たり前になりつつも、日々感染者数の増減に一喜一憂しながら仕事やプライベートを過ごしているかと思います。

最近の仕事の状況を振り返ってみると、○月は...○期は...と一定のリズムで繁閑期があったように思いますが、その傾向もこれまでの傾向を元に予測が難しくなり、急に繁忙期がきたかと思うと急に閑散期がきたりと緩急が激しい日々が続いています。

そんな短期的に来るチャンスを漏れなく掴むためにも、自動化や業務効率化などがより一層求められてきており、エンジニアの役割が重要になってきているように感じています。
そんな中、私もSeleniumを使いE2Eテスト自動化や業務効率化周りで社内の業務改善や効率化に奔走していますが、ある程度システムが増えてきた段階でクラウド化を検討している際にヘッドレス化の部分でぶち当たった問題について備忘メモとして残していきます。

# 起きたこと
任意のディレクトリにファイルをダウンロードできるように`download.default_directory`でパスを指定して通常のモードで実行すると問題なく動作するが、ヘッドレスモードで実行したところ指定した設定が効かずにデフォルトのダウンロードディディレクトリにダウンロードされた。

```typescript:TypeScript
const driver = new Builder()
  .forBrowser("chrome")
  .setChromeOptions(
    new Options()
    .addArguments(
      "--no-sandbox",
      "--disable-gpu"
    ).setUserPreferences({
      "download.default_directory": "./downloads/"
    })
  )
  .build()
```
# 環境
下記の環境で実行しています。

```
macOS Catalina 10.15.x
typescript 4.0.x
ts-node 9.0.x
v14.15.x
chromedriver 87.2.x ※ブラウザバージョンと合わせている
selenium-webdriver 4.0.0-alpha.7
```

# 結論
ChromeのDevToolsCommandを使って設定を行う必要がある。
Seleniumの場合、Executorを使ってSelenium側でカスタムコマンドとして定義されている`sendDevToolsCommand`を実行することでDevToolsCommandを実行することができる。

```typescript:TypeScript
const driver = new Builder()
  .forBrowser("chrome")
  .build()

const session = await this.driver.getSession()
driver.getExecutor().execute(
  new Command("sendDevToolsCommand").setParameters({
    sessionId: session.getId(),
    cmd: "Browser.setDownloadBehavior",
    params: {
      behavior: "allow",
      downloadPath: path.join(process.cwd(), "downloads")
    }
  })
)
```

# もう少し詳しく

## Driverクラスにメソッド定義されている？
最初に色々と探していたときに公式ドキュメントによるとDriverクラスのメソッドにあるような書きっぷりだったけど、GitHubのソースコードを見に行った限りだとメソッドの定義がされていなさそうだった。
ただ、引数などはこちらと同じみたいなので部分的に参考にすると良さそう
https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#sendDevToolsCommand

## Page.setDownloadBehaviorは非推奨になっている
色々と調べている中で`Page.setDownloadBehavior`を使っている記事が多く、私も同じように使ってやった！動いたーとテンション上がっていたんですがよくよく確認してみると、実験的かつ非推奨になっている...
Seleniumを裏で使っているようなフレームワークのPuppeteerのGitHub上のイシューに`Page.setDownloadBehavior no longer works for me (07/21/2020).`と書いている人がいたので、つい最近非推奨になったみたいですね。
https://github.com/puppeteer/puppeteer/issues/1478

DevToolsのプロトコルが記載されているサイトは [こちら](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-setDownloadBehavior)

## Browser.setDownloadBehaviorが代替みたいだけどこれも実験的な位置付け
上記のGitHub上に記載されている内容を見る限りですと、代替は`Browser.setDownloadBehavior`を使うみたいですね。
ただ、こちらも実験的な位置付けなのでいつ大幅な仕様変更があるか分からないので時々確認してく方が良さそうですね。
ビジネス利用する際は、慎重に検討した方が良さそうですね。
rails - devise基礎

# deviseの導入・実装手順
初投稿です。deviseを使った簡単なユーザー管理機能実装についてアウトプットもかねて自分用にまとめてみます。
***
まずは実装の流れになります。

1.Gemインストール
2.devise設定ファイル作成
3.Userモデル作成
4.テーブル作成
5.ビュー作成、編集
6.コントローラーでストロングパラメータ設定
***
1.Gemインストール

```ruby:Gemfile,ターミナル
gem 'devise'
bundle install
rails s
```
2.devise設定ファイル作成

```:ターミナル
rails g devise:install
```
3.Userモデル作成

```:ターミナル
rails g devise user
```

```ruby:config/routes.rb
devise_for :users #自動で挿入される
```
4.テーブル作成

```ruby:db/migrate/20XXXXXXXXX_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :name,               null: false #カラム追加
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end
```

```:ターミナル
rails db:migrate
rails s
```

5.ビュー作成

```:ターミナル
rails g devise:views
```

6.コントローラーでストロングパラメータ設定

```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end
```
実装の基本的な部分は以上です。
***
### devise導入時のエラー
#### rails s ができない　<`const_get': uninitialized constant User (NameError)>
rails g devise:installをしていないのにrails g devise userを行っていた

```:ターミナル
rails destroy model user #同時にマイグレーションファイルも削除しておく
rails g devise:install #ここからやり直し
rails g devise user
rails db:migrate
```
これで一応解決できました。
VRchatで壁を作る

#1. この記事は何？

VRchatでワールドを制作するときに、壁の配置方法について説明します。


前記事：[VRchat(Quest版)でHello world](https://qiita.com/NT1123/items/c305b89a7330d7eddaf4)

#2. どのようにするの?

(1) GameObject -> Create Emptyから空のGameobjectを作成します。
![33.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/f7f62293-740a-9fe3-dc97-23e3de0bfb88.jpeg)

(2) Hierarchy windowに新しく作成されたオブジェクトの名前を「Walls」に変更します。
![34.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/2f2b612e-7739-90d5-c732-818d8823ae0a.jpeg)

(3) 3D Object-> Cubeを作成し、Cubeを先ほど作成したWallsフォルダの中にドラッグします。
![37.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/fc81192b-863e-0bd8-5b48-f376cdcb9f81.jpeg)

(4) CubeをPrefab化します。(同じオブジェクトを複数配置するときはPrefab化すると便利です。） CubeをProjectウインドウのAssetsフォルダにドラッグさせます。
![38.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/dbb30cb6-a7e6-61a3-9868-24ba1817b9e1.jpeg)

(5) AssetでCubeを選択し、Inspectorウインドウのパラメータを入力します。
![40.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/50fc31f7-96f1-cdd2-cc84-8bb2c5a98fc8.jpeg)

(6) Cubeを４つ複製します。
![41.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/700edcb7-6d21-ca3a-e59f-dec310c6a460.jpeg)

(7)各Cubeのパラメータを設定して壁を4辺に配置します。
![43.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/be1ea79d-81a9-dc35-6cc8-0b258fcfacf3.jpeg)

(8)壁に影ができる場合は、「Light」オブジェクトの位置を調整する。
![44.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/25bce0b8-be3b-4fe1-009e-38eba3e6e181.jpeg)


#3. 壁に色をつける

(1)色を作ります。Assets->VR Chat Examples -> TutorialAvator -> Materials でオブジェクトを選択し、着色したい色を選択します。
![49.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/164deda0-4610-7671-78ac-c2babe858efd.jpeg)

(2)オブジェクトを選択します。
![47.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/62591e13-0c69-08f5-9c85-cc3bd1420c8e.jpeg)

(3) (1)で作成した色をオブジェクトに着色します。
![48.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/d1e51427-2e39-2b82-3a79-692d9f03daa5.jpeg)

(4) 壁の色が変更になっています。
![50.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/6fd0dd57-47f6-daac-d783-281947f27b8f.jpeg)
M1 MacにPythonの分析環境を構築する(2020-01-27現在)

個人的メモです。
2020-01-27現在、M1 Macではnumpyやpandasのpipでのinstallに[問題が発生している](https://github.com/numpy/numpy/issues/17807)ため、その回避のためのworkaroundを書いておきます。
いずれ公式にサポートされ、この記事は不要になると思います。

# RosettaでIntel(x86_64)向けバイナリを使う
iTerm2の `Open using Rosetta` にチェックを入れて、普通にセットアップするだけ。

参考：https://alexslobodnik.medium.com/apple-m1-python-pandas-and-homebrew-20f14828ccc7

# Apple Silicon(osx-arm64)向けバイナリを使う
- homebrew： 最近Apple Silicon対応した。`/opt/homebrew` に入る
- python： homebrewで [python@3.9](https://formulae.brew.sh/formula/python@3.9) を入れる（Apple Silicon対応）
    - `/opt/homebrew/opt/python@3.9/libexec/bin` にpathを通す必要あり
- numpy:： homebrewで[numpy](https://formulae.brew.sh/formula/numpy)を入れる（Apple Silicon対応）
- pandas： `pip install python-dateutil pytz` で依存ライブラリを入れ、`pip install cython` を実行した上で `pip install pandas --compile --no-binary :all: --no-use-pep517`
    - 参考： https://github.com/numpy/numpy/issues/17807#issuecomment-733863375
- pipenv： `pipenv --site-packages` でhomebrew下のpackageを参照できる仮想環境を作る
    - 参考：https://pipenv.pypa.io/en/latest/advanced/#working-with-platform-provided-python-components
- poetry： `poetry config virtualenvs.in-project true` でプロジェクト下に `.venv` を作るようにし、仮想環境を作った上で `.venv/pyvenv.cfg` の `include-system-site-packages` を true にして、homebrew下のpackageを参照できるようにする。numpyとpandasはdependenciesには入れない（入れるとビルドしようとするため）
    - 参考：https://github.com/python-poetry/poetry/issues/1393#issuecomment-630791123
よく使うgitコマンド

issue1という名のブランチを作成する

```
git branch issue1
```
issue1にブランチを切り替える

```
git checkout issue1
```

変更したファイルの一覧を見る

```
git status
```
差分を見る

```
git diff
```
変更したファイルを全てステージに追加

```
git add -A
```

特定のファイルをステージへ追加

```
git add ファイル名
```
コミット

```
git commit -m　
```
カレントディレクトリをリモートにプッシュ(originを指定でブランチ名を指定しなくてよい)

```
git push origin HEAD
```

コミットを一つにまとめる

```
git rebase -i
```
ローカルのファイルの変更を元に戻す

```
git checkout <filename>
```

developブランチをpull

```
git pull origin develop
```

これから追加していきます。。。
SinatraでRackを使ったBasic認証を使ってみた。

皆さんは認証には何を使用しますか？
認証と行っても、様々な認証方法があり、最近では外部サービスの認証システムを使用することも増えてきましたね。

今回は、そんな最新の認証ではなく、古くからあるbasic認証をsinatraで使ってみたので、それについてお話しします。

# 0. 環境とbasic認証を使用したサイトについて。
OS: Ubuntu 18.04.5 LTS
Ruby: ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Rack: 2.0.7

サイトについては、2週間に一回30分程度しか使用しないサイトで、想定人数は6人です。
使用するユーザについては決まっているサイトで、部外者のアクセスを禁じたかったので、basic認証を使いました。

### なぜ、basic認証を使ったのか。

basic認証を使った理由は、Rackを使ったbasic認証ではDBとの連携や、環境変数(env['REMOTE_USER'])からどのユーザがアクセスをしているかなどを確認できるため、自分で書く必要性がないと思ったからです。

# 1. 実装

Rubyファイルに以下のコードを追加することにより、アクセス時にbasic認証を求められます。

```ruby:app.rb
use Rack::Auth::Basic do |username, password|
　　　username == 'Qiita' && password == 'LGTM'
end
```

仕組みはusernameとpasswordにユーザが入力したデータが代入されて、2行目の部分で合っているか認証を行います。

Rackの仕様状Rack::Auth::Basicの中身がTrueであればログイン、Falseであればもう一度認証が表示されるので、合っていればTrue,間違っていたらfalseを出すプログラムを作れば簡単にログイン機能を実装できます。

### ActiveRecord
Rack::Auth::BasicとActiveRecordを組み合わせて使うことができます。

```ruby:app.rb
use Rack::Auth::Basic do |username, password|
    member = Members.find_by(username: username)
    if member && member.authenticate(password)
        true
    else
        false
    end
end
```
このように、ActiveRecordのauthenticateを使って認証を作ることが可能です。
誰がログインしたいか取得する際は`env['REMOTE_USER']`を使用することで確認できます。

if文を使用しないで、実装する方法（@scivolaさんに教えていただきました。）

```ruby:app.rb
use Rack::Auth::Basic do |username, password|
   Members.find_by(username: username)&.authenticate(password)
end
```
# 2. 最後に
正直な話、SNSやショッピングサイトなどの第三者がユーザになるサービスや大規模な社内サービスに使うのはやめておいた方がいいと思いますが、小規模な社内サービスやローカル内でのサービスなどちょっとした認証に使いたい場合はいいと思います。

簡単にできるので、試してみるだけ試してみましょう。
spring-kafkaのKafkaListenerでtopicsなどをメソッドやプロパティ経由で指定

spring-kafkaの```KafkaListener```で```topics```などは基本的にはプロパティで指定するが動的にメソッドやプロパティ経由で指定したい場合がある。その場合```beanRef```と組み合わせて実現する。

```java
@Component
public class SampleConsumer {
	public String getMyTopic() {
		return "mytopic3";
	}
	
//	public String myTopic = "mytopic3";
	
	@KafkaListener(topics = "#{sample.myTopic}", beanRef = "sample")
	public void processMessage(
```

<code>beanRef</code>にはこのbean自身を指す名前をつける（デフォルトは```__listener```）。これでSpELでbeanを参照可能になるので、メソッドやプロパティが呼べる。上記例は```#{sample.myTopic}```で```getMyTopic```を呼んでいる。コメントアウトしている```myTopic```プロパティも使用可能。
MacでVPNを接続するまでリトライするコマンド

## はじめに

仕事でVPNを常時接続しているのですが、これがなかなか繋がらないわけです。
ひどい時は何回も接続を試して30分以上経ってやっと繋がる、なんてこともあります。
そんなこんなでVPNの接続が失敗する度にマウスでカチカチして接続を繰り返すのが面倒になったので
自動でリトライまでしてくれるVPNの接続コマンドをbashスクリプトで自作しました。

## コマンド

```bash:インストール
$ touch /user/local/bin/vpn_connect
# /user/local/bin/vpn_connectに下のコードを貼り付ける
$ chmod +x /user/local/bin/vpn_connect
```

```bash:使い方
# vpn_connect [VPNサービス名]
$ vpn_connect '俺のVPN'
Connecting...
Connected
```

```bash:/user/local/bin/vpn_connect
#!/usr/bin/env bash

while :
do
  vpn_status=`cat <<EOL | bash
scutil --nc status '$1' | head -n 1
EOL
`

  case $vpn_status in
    Disconnected)
      cat <<EOL | bash
networksetup -connectpppoeservice '$1'
EOL
      echo "Connecting..."
      sleep 1s
      ;;
    Connecting)
      sleep 1s
      ;;
    Connected)
      echo $vpn_status
      osascript -e 'display notification "VPN is connected" with title "vpn_connect" sound name "Submarine"'
      exit 0
      ;;
    *)
      echo $vpn_status
      exit 1
      ;;
  esac
done
```

## おわりに

shell力はザコなのでてきとーに作ったものですが便利そうなので良ければ使ってやってください。
（すんなりVPNが繋がればこんなことしなくていいのに・・・）
deviseを利用したログイン機能の実装

#目次
<b>①deviseとは</b>
<b>②deviseのインストールの仕方</b>
<b>③モデルの作成方法</b>
<b>④テーブルの作成方法</b>
<b>⑤ビューファイルの作成</b>
<b>⑥deviseで使えるメソッド</b>

##①deviseとは
Railsで作成したアプリケーションへ簡単に認証機能を実装することができるgemのひとつ。
こちらを使用すると、ログインやサインアップなどのログイン機能を1から構築することが簡単に実装することができる。

##②deviseのインストールの仕方
Gemfileに以下のように記述する

```php:Gemfile
   gem 'devise'
```
その後に、gemをインストールをする

```php:ターミナル

   bundle install
```
次に、設定関連に使用するファイルを自動で生成する

```php:ターミナル

   rails g devise:install
```

##③モデルの作成方法
deviseを用いてuserモデルを作成する

```php:ターミナル

   rails g devise user
```

##④テーブルの作成方法
rails g devise userで生成されたマイグレーションファイルが存在するか確認し、確認できたら

```php:ターミナル

   rails db:migrate
```

##⑤ビューファイルの作成


```php:ターミナル

   rails g devise:views
```
上記のコマンドを実行すると,app/views/deviseの配下に以下のディレクトリが作成される

◯sessions：ログイン画面
◯registrations：ユーザ登録画面とアカウント編集画面
◯confirmations：認証メール再送信するための画面
◯passwords：パスワードを変更するための画面
◯unlocks：アカウント凍結画面
◯shared：Deviseの画面に遷移させるためのリンク

##⑥deviseで使えるメソッド

|メソッド|意味|
|:---|:---|
|before_action :authenticate_user!|コントローラーに設定して、ログイン済ユーザーのみにアクセスを許可する|
|user_signed_in?|ユーザーがサインイン済かどうかを判定する|
|current_user|サインインしているユーザーを取得する|
|user_session|ユーザーのセッション情報にアクセスする|
ローカルネットワークに繋がっているデバイスを検索列挙

こんにちは
ローカルネットワーク（```192.168.1```）に繋がっているデバイス（の IP アドレス）を検索列挙しました[^1]。```ping```コマンド利用です。

[^1]: 参考情報：「[LAN内で使われているIPアドレスを調べる6つの方法(Linux/Windows)](https://orebibou.com/ja/home/201505/20150518_001/)」

```
$ ./ip_search.sh 192.168.1
9 bytes from 192.168.1.1: icmp_seq=0 ttl=64
9 bytes from 192.168.1.2: icmp_seq=0 ttl=64
9 bytes from 192.168.1.4: icmp_seq=0 ttl=128
```

```ip_search.sh
#!/bin/sh
domain=$1
seq -f "$domain.%g" 254 | xargs -P256 -n1 ping -s1 -c1 -W1 2>/dev/null | grep ttl | sort
exit
```

### ping 以外のコマンドを利用
ping 以外のコマンドを利用し同様な情報が得られるとのことです。

#### ip コマンド

```console
$ ip neigh | grep 192.168.1
```

#### arp コマンド

```console
$ arp -a | cut -d ' ' -f 2 | tr -d '()' | grep 192.168.1
```

#### arp-scan コマンド

```console
$ sudo arp-scan -I en0 -l | grep 192.168.1
```

#### nmap コマンド

```console
$ sudo nmap -sP 192.168.1.* | grep 192.168.1
```

#### ifconfig　コマンド
```ifconfig``` コマンドの利用例です。gnu-grep コマンドの perl 拡張を使っています。

```console
$ ifconfig -a | grep broadcast | grep -ohP "\d+\.\d+\.\d+" | head -n1
192.168.1
```
Serverless Laravel を振り返る※お金のお話もあるよ！

※2022/01/04　運用後に ElastiCache -> DynamoDB にした部分を追記＆修正


**AWS Lambda x Laravel** でサーバーレス構築 ⇒ 本番リリースまで実施しました。

ベストプラクティスではなかったり、情報を見落としていたりもあると思いますが、
後々の ***Serverless Laraveler*** のために『こんな感じでしたよ』というのを残しておきます。

あと**費用感**。とても大切。

# 想定される読者像
* **AWS 完全に理解した！**って人。
* Laravel で ***Serverless*** したい人。
* ***Serverless*** は理解しているけど『**実際の費用感どうなん？**』と思っている人。

# 背景とお気持ち

* 情報公開時にアクセス/トランザクションがスパイクするようなWEBサービスを作る
* 小規模開発なので予算は可能な限り抑えたい
* でも**開発環境で本番環境と同等の検証ができる**ようにしたい
* あと開発環境をわざわざ Start / Stop するようなものは作りたくない
* 本番環境は可能な限り自動的にスケールしてほしい
* 最近の Lambda は VPC でも早くて、PHP もいけて、Laravel も動くらしい
* **僕は Laravel が好き**

⇒これは AWS で ***Laravel Serverless*** するしかない！　やろう！！！

# アーキテクチャ

基本的には下記の **AWS 公式**の記事のようにしました。
[サーバーレス LAMP スタック – Part 4: サーバーレス Laravel アプリの構築](https://aws.amazon.com/jp/blogs/news/the-serverless-lamp-stack-part-4-building-a-serverless-laravel-application/)

異なる点や補足等は以下の通り。

* Lambda は VPC 内で動かす。

* API Gateway の手前には CloudFront を**通していない**。

* DB には Aurora Serverless を採用。
また、開発環境のみ**一定時間アクセスがないとDBを止める**ように設定。

* ~~セッション管理で ElastiCache(Redis) を入れた。~~ ⇒ 運用後に DynamoDB に変更
 
* SES からメールを送るために SMTP インターフェース用の VPC endpoint を引いている。

* デプロイ作業や SSH トンネル用に、EC2 インスタンスがある。

# ここ好き！ポイント

### 本番環境同等の検証ができる
それだけ。でも、とても嬉しい。

### Aurora Serverless でDBがスケーリングする
* 開発環境：使うのは検証用の週１ぐらい、使うときには本番環境同等の検証がしたい
* 本番環境：ユーザースパイクに合わせてDB性能も勝手にスケールして欲しい

というところを叶えてくれるポイントになった [Aurora Serverless](https://aws.amazon.com/jp/rds/aurora/serverless/)。

取り分け良いのが、一定時間アクセスがないと自動的に停止する機能で、
**停止時には稼働料金が掛からなくなる**。ありがてぇ。
※停止後にアクセスすると１分ぐらい起動に時間は掛かるが、自動的に起動する。
※もちろん、本番環境は停止させない設定にできる

### Lambda がとにかく安い
Google Analytics でピーク時 DAU 1500 人の利用があったが、
それでも無料利用枠の範囲内に収まったので **$ 0.00 / month** になっている。


# 困ったこと

### セッション管理が Cookie だと Cookie が無限増殖してクライアントがこける

決済用の外部サイトに飛んで戻ってを繰り返すと、
**Cookie が無限増殖してクライアントがこける**。

~~ElastiCache(Redis) の単一ノードでセッション管理して対応。~~
~~**$20.0 / month**。苦い。~~
~~スケールしないし、単一障害点にもなってしまった。超苦い。~~
⇒2021年中にセッション管理を DynamoDB（オンデマンド）に切り替えて **$0.09** 程になりました。

### Lambda が VPC 内にいるのでネットに出られない

セキュリティが担保されたり VPC 内 RDS や ElastiCache にアクセスできたりと恩恵はあるが、
AWS SES で Laravel の MAIL_DRIVER='ses' でメール送信できない、という悲しみ。
対応として、VPC endpoint から SMTP インターフェースでメール送信できるようにした。

**$10.0 / month**。苦い。

※NAT Gateway か NAT インスタンスで対応はできるらしい。
　が、NAT Gateway が $60.0/month になりそうだったり、そもそも管理対象を増やしたくなかったので見送り。

### X-Ray が使えなかった
そもそも **PHP** だと使えないみたいだった。しょんぼり。

# 気にしていたけど杞憂だったこと

### ElastiCache のサイジング

スケールせず単一障害点になってしまったので、メモリ容量を懸念。

Google Analytics でピーク時 DAU 1500 人ぐらい。
ビビって t3.small にしていたがメモリ使用率 1% にも満たなかったので t3.micro でも数千/数万は捌けそう。
※その際は NW 速度がボトルネックになるかも

⇒現在は DynamoDB（オンデマンド）になりました。

### Aurora Serverless のサイジング

勝手にスケールするので、料金を懸念。

Google Analytics でピーク時 DAU 1500 人ぐらい。
MySQL の FOR UPDATE ロックが絡むような処理もそこそこあったが、
最後まで１番小さいサイズ（ 1 ACU で t3.medium より安い）で稼働できていた。

# 費用感

だいたいのね。

**これが見たかったんでしょう？**

### 開発環境
⇒**Total: ~~$60.0~~ $40.0 / month**

* ~~ElastiCache(t3.micro) $20.0~~
* RDS(Aurora Serverless) $15.0
* EC2(t3.micro) $15.0
* VPC endpoint $10.0

### 本番環境
⇒**Total: ~~$120.0~~ $100.0 / month**

* RDS(Aurora Serverless) $70.0
* ~~ElastiCache(t3.micro) $20.0~~
* EC2(t3.micro) $15.0
* VPC endpoint $10.0
* CloudFront $5.0

# まとめない

以上。
PowerShellでIE画面の内容を出力する

#背景
PowerShell課題を解いていきます。

#課題
PowerShellでIE画面の内容を出力する。

#IEを立ち上げ、任意のページを開く
```
$ie = New-Object -ComObject InternetExplorer.Application #IE起動        
$ie.Navigate("https://www.google.com")                   #URL指定
$ie.Visible = $true                                      #表示
While($ie.Busy)                                          #読み込みが終わるまで待機
{Start-Sleep -Seconds 1 }
$doc = $ie.Document                                      #画面情報取得
```
#PowerShellでIEの内容（テキストやボタンの文字）を取得する
```
$btntxt = $doc.getElementsByName("btnK")                 #ボタン文字の取得
```
#取得した内容を、PowerShellコンソールに出力する
```
Write-Host $btntxt[0].value -ForegroundColor Green
```

#実行結果
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642378/63daf012-a702-e51f-2909-d51d8f8b642b.png)

#まとめ
実行できたけど、たまにエラーになるときがあります。
なぜだろう。
改善したほうがいいポイントなどあればご指摘ください。
マイクロサービスにおけるトランザクション分離性(Isolation)の問題

マイクロサービスについて色々と探求しているうち、「分離性」について混乱してきました。そこで、自分なりに整理してみようと思います。

# ACID特性における分離性（Isolation）とは
分離性とは、トランザクション実行中の途中経過が、別のトランザクションから観測できない、という性質です。

例えば、トランザクションAが
　①田中さんの口座残高を３万円減らす
　②山田さんの口座残高を３万円増やす
という処理をする場合、別のトランザクションBは「①だけが完了ている。②は未完了。」という時点のデータを参照できない、ということです。

これができてしまう現象はダーティーリードであり、これができてしまうとデータの整合性を保てません。

分離性が満たされている場合、あるトランザクションBは、以下の２つしか観測できません。
　・別のトランザクションAが全く実行されていない状態
　・別のトランザクションAが完全に実行された状態

## 誤った説明（注意：あくまで私見です）
一般的な説明としてよくあるのが「分離性とは、並列に実行した場合と逐次的に実行した場合とで、結果が同じになること」というものです。しかし、これは誤りかなと思っています。分離性とは本来上記の概念（途中経過を外側から観測できない）を指すのであり、分離性があるだけでは、並列実行と逐次実行とで異なる結果になる可能性があります。

例えば、トランザクションAの実行中に、別のトランザクションBがテーブルに１件のレコードを追加したとします。トランザクションAがトランザクションBの処理の前と後でテーブルの件数を取得すると、前と後ではテーブル件数が異なります（ファントムリード）。トランザクションAが、このテーブル件数をもとに何らかの処理をするとすれば、逐次的に実行した場合と結果は異なる可能性があります。

上記のケースでは、「並列に実行した場合と逐次的に実行した場合とで、結果が同じになる」ということは、どう頑張っても実現できません。

なお、 [wikipedia「ACID (コンピュータ科学) 」](https://ja.wikipedia.org/wiki/ACID_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E7%A7%91%E5%AD%A6))によると、ACID特性の正式な定義は「ISO/IEC 10026-1:1992 Section 4」にあるとのこと。ここに分離性がどう定義されているのかが気になってきました。というのも、インターネット上を検索して出てくるACID特性の定義は「野良」なものばかりで原典をそのまま載せているものが無さそうだったからです。「ISO/IEC 10026-1:1992 Section 4」をインターネット上で閲覧することはできなかったので、図書館に行って読んでみようと思います。

# 「分離性」と「トランザクション分離レベル」との関係

| トランザクション分離レベル | 分離性に着目した説明 |
|:-----------|:------------|
| READ UNCOMMITED       |分離性を満たさないレベル|
| READ COMMITED     |分離性を満たすレベル|
| REPEATABLE READ       |同一のデータを読み込む複数のトランザクションを逐次的に実行する。例えばトランザクションAがあるデータを読み込んだら、他のトランザクションBはトランザクションAが完了するまでそのデータを読み込めない。同時に実行しないので分離性はもはや関係ない。|
| SERIALIZABLE       |同時に発生したトランザクションを、その内容に関わらず交通整理して本当に逐次的に実行するレベル。同時に実行しないので分離性はもはや関係ない。|

# マイクロサービスと分離性
マイクロサービスでは、上記の意味での分離性が満たされません。例えば、２つのマイクロサービスに跨って更新処理を行う場合、１つ目のサービスで更新処理が完了したが２つ目のサービスでの更新処理は未済という場合、１つ目のサービスの更新結果はすぐに他の処理に見えるようになってしまいます。

複数のサービスに跨って更新処理を行う場合、原子性（Atomicity）を心配されることが多いような気がしています。つまり、どこか一部のサービスが失敗した場合に、他のサービスをロールバックしなければならない、ということに注目が集まりやすいです。

その点ももちろん重要なのですが、分離性が満たされないことによって生じる悪影響をいかに扱うか、ということも同じくらい重要です。

# マイクロサービスにおける分離性との付き合い方
あまり定説は無いような気がしますが、Sagaという手法が現実的なように感じています。これについては、また別の記事でご紹介できればと思います。
AWS IAMのルートアカウントのみが行える操作/権限について

初投稿のため練習と学習記録を兼ねてます。
お見苦しいかと思いますが、ご容赦ください。

#ルートアカウントと管理者権限ってほとんど変わらない……？
IAMについて学習していくうちに__「ルートアカウントとIAMユーザーの管理者権限はほぼ同じ」__なんて解説されているのを見かけたので、(じゃあ設定する必要あんまりなく無い?逆に違いって何？)と思ってまとめてみました。
同じ疑問を抱いた僕のような初学者の方のお力になれれば幸いです。

以下は(僕が調べられた範囲での)ルートアカウントのみ可能な操作になります。

+ AWSルートアカウントのメールアドレスやパスワードの変更
+ AWSアカウントのサービス（公式サポート等）の申込/キャンセル
+ AWSアカウントそのものの停止
+ IAMユーザーからの課金情報へのアクセスに関するactive/deactive
+ 他のAWSアカウントへのRoute53のドメイン登録の移行
+ コンソリデイテッドリビングの設定
+ 脆弱性診断フォームの提出
+ 逆引きDNS申請

それぞれ見ていきましょう。

<dl>
<dt>・AWSルートアカウントのメールアドレスやパスワードの変更</dt>
<dt>・AWSアカウントのサービス（公式サポート等）の申込/キャンセル</dt>
<dt>・AWSアカウントそのものの停止</dt>
<dd>説明不要ですね。これは分かりやすいと思います。</dd>
</dl>

<dl>
<dt>・IAMユーザーからの課金情報へのアクセスに関するactive/deactive</dt>
<dd>通常の設定だと、IAMユーザーから課金情報へアクセスすることは出来ません。</dd>
<dd>各IAMユーザーに対してポリシーを設定することによりアクセス可能となります。</dd>
</dl>

<dl>
<dt>・他のAWSアカウントへのRoute53のドメイン登録の移行</dt>
<dt>・コンソリデイテッドリビングの設定</dt>
<dt>・脆弱性診断フォームの提出</dt>
<dt>・逆引きDNS申請</dt>
<dd>これらについては申し訳ないのですが、知識が足りず理解が追いついていません。</dd>
<dd>今後追記していきたいと思います。</dd>
</dl>

以上となります。
[Azure] Azure Event Hubsのイベントをブラウザでリアルタイム受信するサンプル

# これはなに？

Azure Event Hubsのコンシューマ（イベント受信側）をWebブラウザで動かすサンプルです。

ソース全体は https://github.com/sengokyu/Ex.AzureEventHubConsumerOnBrowser に置いてあります。

# 実行の仕方

## 事前準備

Event Hubを作成し、接続文字列を取得しておきます。

## 実行

環境変数を3つ設定します。

| 環境変数 | 内容 |
|:--|:--|
| EVENTHUB_CONNECTION_STRING  | 取得した接続文字列です。  |
| EVENTHUB_CONSUMER_GROUP  | 既定では`$Default`です。  |
| EVENTHUB_NAME | 作成したEvent Hubの名前です。  |

パッケージをインストールしてビルドします。

```console
npm install
npm build
```

`dist/index.html`をブラウザで開きます。開発者コンソールを開きます。

イベントを送信してみます。

```console
node src/producer/send-event.js
```

# 実行結果

このような感じにイベントが届きます。

![Screencast-2021-01-27-20_46_18.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59475/b90d0859-9ff8-8279-a4fa-08c699bd637f.gif)


# はまったところ

Webpack 5では`process`を定義してくれなくなったため、自前で`webpack.config.js`に書く必要がありました。

# この実装の弱点

接続文字列がJavaScript内にあるので、外からはアクセスできない環境でしか活用できません。
【IQ Bot】オンプレ版セッティングファイルのやっておくと便利な設定


この記事は、以下の記事のサブカテゴリです。

[IQ Bot インストール手順](https://qiita.com/IQ_Bocchi/items/529dc72b6ab3d26dbfe0)　＞　[（７） IQ Botインストール後の諸設定](https://qiita.com/IQ_Bocchi/items/b75d84be6784f5fd51fb)

# やっておくと便利な設定

この記事では、オンプレでIQ Botを入れたときに、「やらないと動かないわけじゃないけど、やっておくとあとあと便利」という設定たちをご紹介します。

##(1) よくアクセスするフォルダへのショートカットをデスクトップ上に作っておく

以下のフォルダのショートカットをデスクトップ上に作っておくと便利です。

|#|概要|説明|
|:-----------------|:------------------|:------------------|
|①|Configurationsフォルダ|IQ Botの設定ファイルがひととおり入ったフォルダ。<br>デフォルトのフォルダパスは以下（A2019の場合）：<br>C:\Program Files (x86)\Automation Anywhere IQ Bot A2019\Configurations|
|②|Outputフォルダ|RPAからIQ Botに帳票をアップロードしたときに、出力結果が入るフォルダです。(詳細は「[IQ Bot：出力の流れ](https://qiita.com/IQ_Bocchi/items/f90188352947ae054ab8)」を参照)<br>デフォルトのフォルダパスは以下（A2019の場合）：<br>C:\Users\Public\Documents\Automation Anywhere IQBot Platform\Output|
|③|ログフォルダ|IQ Botのログが吐かれるフォルダです。<br>デフォルトのフォルダパスは以下（A2019の場合）：<br>C:\Users\Public\Documents\Automation Anywhere IQBot Platform\Logs|

ちなみに筆者はこんなかんじ↓で①と②のショートカットをデスクトップに置いておき、ログ（③）が必要なときは②からGUI上でフォルダ遷移しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/ed921cbb-d62d-89da-b3ea-5a03b51d21e6.png)

##(2) Settings.txtのデフォルト設定

上記①のフォルダの中にあるSettings.txtを、以下のように変更しておくと便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/a3b3c9dc-9d90-1cc0-d991-6eee59ba7886.png)

以下に解説を。

###① OCREngineのパラメータ

ラーニングインスタンス作成画面の、ここ↓の選択肢のデフォルト値を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/7ec49fbb-20da-d129-337a-00e5a6cb85a4.png)
あくまでもデフォルト値なので上記の画面から切り替えられるのですが、筆者はよく切り替えを忘れてしまうので、個人的に一番使用頻度の高いABBYYをデフォルトにしています。

ABBYY以外をデフォルトにしたい場合は、以下の要領でパラメータを設定します。
「そもそも、どのOCRがいいの？」と迷う場合は、「[OCR選定のコツ](https://qiita.com/IQ_Bocchi/items/8ad6ec8f5a6afd3c98a1)」をご覧ください。

|OCR|パラメータ設定内容|備考|
|:-----------------|:------------------|:------------------|
|Tesseract4|Tesseract4|
|ABBYY|Abbyy|
|Microsoft Computer Vision|MicrosoftAzureAPI|
|Google Vision|GoogleVisionAPI|
|Tegaki|TegakiAPI|Tegaki利用時には、別途申し込みが必要になるので注意！（画面から選択するだけでは使えるようになりません。）|

###② GroupDocumentThresholdのパラメータ

「[IQ Bot：出力の流れ](https://qiita.com/IQ_Bocchi/items/f90188352947ae054ab8)」の記事の中の「[*5：新グループ作成の閾値とは](https://qiita.com/IQ_Bocchi/items/f90188352947ae054ab8#5%E6%96%B0%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E4%BD%9C%E6%88%90%E3%81%AE%E9%96%BE%E5%80%A4%E3%81%A8%E3%81%AF)」で説明している「新グループ作成の閾値」のデフォルト値を決めるのがこちらのパラメータです。

リンク先に色々と説明が書いてありますが、個人的にはこの閾値は1にしておくのが最もIQ Botの良さを活かせる使い方になると思っているので、最初から1に設定しています。

こちらもSettingsファイルの値はあくまでデフォルト値なので、画面からも修正できるのですが、個人的には**修正をめちゃくちゃ忘れやすい**ので最初から1にしている次第です。

# 他には……？

いかがでしたか？

この記事ではIQ Botをオンプレで入れた際に、筆者が個人的にやっておくと便利だと思っている設定をご紹介しました。

他にも「こういうこともやっておくと便利だよ！」などあれば、コメントやTwitterへのリプなどで教えていただければ幸いです。

また、[ABBYYの有効化](https://qiita.com/IQ_Bocchi/items/e8e9ae41b398209186ed)のようなほぼマストの設定をしていない人は、そちらを先にやりましょう。

では！
Docker × Laravel × ngrok ローカル環境を一時的に外部へ公開する

仕事で使うかな？と思って導入したら結局使わなかったので投稿して供養します。。

## ngrok(エングロック) とは

https://ngrok.com

localhostで動いているサーバーを外部(LAN外)からアクセスできるようにリレーしてくれるツールです。

無料で使用できます。

## 環境

- PHP: 8.0.1
- Laravel: 8.23.1

## Docker X Laravelとは

Docker Composeを使用してシンプルなLaravelのローカル開発環境を構築するツールです。

- https://github.com/ucan-lab/docker-laravel
- [最強のLaravel開発環境をDockerを使って構築する【新編集版】](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)

## 導入

```yml:docker-compose.yml
services:
  # ...

  ngrok:
    image: wernight/ngrok
    ports:
      - ${WEB_PORT:-4040}:4040
    environment:
      NGROK_AUTH: ${NGROK_AUTH}
      NGROK_PROTOCOL: http
      NGROK_PORT: web:80
      # SSLの場合
      # NGROK_PROTOCOL: https
      # NGROK_PORT: web:443
```

https://dashboard.ngrok.com/get-started/setup
サインインして authtoken を取得します。

`.env`

```
NGROK_AUTH=hogehogehogehogehoge
```

ちなみにサインインなしだとセッションが2時間までになります。

```shell-session
$ docker-compose up -d
```

http://localhost:4040

表示されているURLをクリックすると確認できます。

![ScreenShot 2021-01-27 20.35.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/81065ca2-edda-979e-d560-47e7a3da99f0.png)

表示されるURLをクリックすると...

![ScreenShot 2021-01-27 20.36.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/d9cd988a-1430-990f-b600-ebe6bb878e50.png)

LaravelのWelcome画面が表示されました！

![IMG_6801.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/79debbcb-5462-dfc3-161c-616aac2b5dad.png)

外部のPCやスマホからアクセスできてるのでokです！
また、コンテナを破棄すれば接続できなくなります。(まぁ当たり前ですが...)
診断サービス？をつくってみたんだけども


# 開発者のレベル
ポケットモンスター緑・赤で例えるならばタケシに会うレベル
~~動いたらいいな♡って念じながら~~プログラミングをやっています



#診断サービスをつくろうと思った理由
アプリを開発している知り合いのボドゲ―マーに「診断アプリはオススメだよ」って言われたからです。
いつかアプリ開発にも手を出したいよ～～。
ゲームとかもつくりたい。
~~そして広告収入という名の不労所得がほしい。~~



#つくったサービス

![title001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655368/0c951337-3989-9b8f-2ed8-86a22985bd5a.jpeg)
http://myzm.sakura.ne.jp/

99%の確率で私の好きなボードゲームであるバラージをおススメしてくれる私得の診断サービスです。出オチ100%である。
実はsakuraのサーバーに移植する前はAWSのS3にコードを放り込むだけという適当なものをTwitterで公開してました。
遊んでくれた方々に感謝。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">バラージを布教したいので、99%の確率でバラージをおすすめするボドゲ診断サービスを作ってみました！　<a href="https://t.co/jSB24ZYWug">https://t.co/jSB24ZYWug</a> <a href="https://t.co/w2dq7kcOuZ">pic.twitter.com/w2dq7kcOuZ</a></p>&mdash; 黛 (まゆずみ) (@t12200301) <a href="https://twitter.com/t12200301/status/1312306435452071939?ref_src=twsrc%5Etfw">October 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



#どうやってつくったか？
Webサイトのテンプレートについては下記を参考に自分好みに改造していきました。
https://templatemo.com/tm-539-simple-house

選定基準としては 
1. Javascriptを利用している（少しでもJavascript触りたい)。 
2. 好み
3. シンプル　


診断機能についてはこちらの記事を参考にしました。
https://qiita.com/bstyle6130/items/c3dfebdceaaef8e3860d

なんとな～くは理解できるけど０からこれを作れと言われたらif文をがりがり回すしか思いつかないので、
先駆者の方に感謝！


#改善点
私の今の力ではすぐに解決できないな～っていうのは下記です。
技術力が高まったらあとで直したいのでメモしておきます。


###1.診断結果をシェアするときに画像も一緒にツイートさせる。
Twitterの性格診断なんかでよく見る形かと思います。
Twitterのシェア機能では画像を一緒に添付されているのですが、Twitterボタンの生成機能に画像情報を付記する項目が無いので、おそらくこれはTwitterカードをresultごとに動的に生成しているのではないかと予想。
となると、それを自力でなんとかするのは今の段階では無理なので、ちょっと保留にしておきます。

###2.コードに対する理解を深める。
正直なにがどうなって動いているの？ってところもあるので、もうちょっと修正を効かせるために理解を深めたいです。

###3.httpをhttpsにしたい。
この領域に関してはちゃんと理解したいので改めてまとめます！

#バラージとはなにか？
バラージは第一次世界大戦後のヨーロッパを舞台に、各国の発電会社のCEOになって発電するボードゲームです。
![test01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655368/f602e7ce-ca7c-a764-1510-0e25142e5195.jpeg)


#つくってよかったこと
バラージをやる頻度が高まったよ！

あとはサービスの原理を知っているのと実際にプロダクト？として出すのとでは大きく違うなあという月並みな感想です。
次は何しよっかな～～。