OmniAICreator/Qiita-1.07M huggingface dataset:
使いやすい管理画面を作るために考えておきたいと思ったこと

## はじめに
管理画面を開発しているときに、いくつかポイントがあるなと思ったのでアウトプットします。

## 現場で行われるやり取りやコミュニケーションを考慮する
### URLでの共有
現場では
「こういう検索結果のこういうユーザーに対してこういう操作をしてください！」
というふうに他の人にURLを共有して指示を出したり、
「今月の対象はこの人達だから、確認お願い！」
というふうにコミュニケーションを取りながら業務を進めます。

**ある状態のページをURLを通して共有したいことは多々あります。**
URLに状態をもたせることは重要ですし、URLで表現できたら便利なことはたくさんあるのではないかなと思っています。

管理画面の開発をするときは、
**そのページがどのようなコンテキストで共有される可能性があるのか、URLで表現できたら便利なことは無いかを考えてみる**
と良いと思います。


### やり取り、コミュニケーションに必要な情報がしっかりと表示されているかどうか
例えば
「ID何番のユーザーさんって今どういう状態？」
というふうにシステムを使ってもらう人たちの間でやり取りが発生します。
こういったやり取りを円滑にするためには管理画面上でやり取りやコミュニケーションで利用される情報は表示させておく必要があります。
**現場の人たちがやり取りをしながら、コミュニケーションを取りながら業務を進める上で必要な情報をすべて提供できているのかを考えてみることは重要だと思っています。**

### コピー&ペースト
実際に現場の人にシステムを使ってもらうと、画面に表示されている文字をコピーして貼り付けるということが意外とされていたりします。

例えば一覧ページでテーブル状のデータで横1行すべての範囲でクリックしたら詳細へ飛んでしまうようなページがあったとして、実は横1行のある部分（例えば企業名など）をコピペしたいのに、コピペしようとしたら詳細に遷移してしまって使いづらい。。。
といったことが発生したりします。
（この場合は横1行すべての箇所をクリックしたら遷移しないようにして、詳細へ遷移するようのボタンを設けるなどしてあげると良さそうです。。。）

もし良くコピペされるような箇所があればコピーボタンを配置しても良いかも知れません。

## 操作を実行する前にシステム利用者が安心する情報を表示しておく
たとえば強制申請のようなボタンがあったとして、「本当は関係者にメールで通知してほしくないけどシステムは本当にそうなっているのか？」「もしメールが送信されるとしたらどのメールアドレスに送信されるんだろう？」というふうに心配になってしまうと思います。

ですので強制申請ボタンを押したら、そもそもメール通知がされるのかどうかや通知されるとしたら通知先のメールアドレスを表示してあげたりすると、システムを使う人が安心して強制申請ボタンを押すことができます。

他にもたとえばチェックしたユーザーに対して一括で何かを実行する機能があれば、実行前に選択したユーザーの名前やメールアドレスを表示してあげたり、
何か操作する前に、その操作をする人が知りたい情報、安心できる情報（自分で確認できる幅）を表示しておき、その情報を確認後に操作を実行できる　というふうにすると安心して操作できる管理画面になるかと思います。

## 他のシステムやソフトウェアも同時に利用しているという背景を汲む
現場の人はなにも管理画面だけを使って業務を進めるわけではありません。
（管理画面ですべて実現できれば本当は良いのですが、、、）

スプレッドシートやエクセル、外部のシステムと並行で管理画面を触ることも多いと思います。

そのときに管理画面からCSVでデータを出力できれば、スプレッドシートやエクセルで加工しやすかったり、外部のシステムと連携できたりして現場の人の業務を楽にするお手伝いができるかも知れません。

一覧ページなどを作る時に特に考慮が必要ですが、
**現場の人の、その一覧ページのデータを使って行う業務などはないか、現場の人に話を聞くことが重要だと思います。**

## 管理画面を利用する人の属性ごとの目線で考える
例えば管理画面を営業さん、経理さんに使ってもらうのだとしたら、それぞれの属性によって表示されていてほしい情報などは違ってきます。

そこも考慮できていないと、営業さん的には使いやすいけど、経理さん的には使いづらい管理画面（逆もしかり）が出来上がってしまいます。

## ステータスや状態での検索
「今こういう状態のデータを出したい」
「今こういうステータスの人がどれくらいいるか知りたい」
「こういうステータスの人に対してこういう操作がしたい」
というふうにステータスや状況で絞り込みをする場面は多く、現場の方からいただく要望でもこういうふうに検索できるようになると嬉しいです！というものが多いです。

**管理画面で一覧画面など担当した際はどういうときにどういうデータを検索できれば現場の人が嬉しいんだろう？と考えることが重要だと思います。**

## 備考やメモ欄を考慮する
現場の業務では【こういう内容をメモとして残している、備考として残している（それがルール）】という場面があったりします。
現場の業務フローに、メモを残すこと、備考を書くことが組み込まれている場合はそのことも考慮する必要があります。

## より少ないステップでより複雑で大きなことを実現できるように
てこの原理のように、小さな力でより大きなことを成し遂げられるような管理画面は理想なのだろうなと思っています。

システムを使っていただく人からの要望でも、もっと楽にこれできるようになったらな〜というものが多く、

**小さな力で大きなことを実現できるようにすること自体に大きな価値がある**　ということを今回の管理画面の開発を経て再実感しました。
（システム開発の本質的な部分なのかもしれない。。。）

## 最後に
**管理画面の開発をするときは現場で発生する現場の人同士のやり取りやコミュニケーションを停滞させないように、スムーズにいくように設計することが重要になってくるのではないかと思っています。
（より小さい力でより大きなことを実現できるように　という部分ももちろん重要）**

そしてこの現場でのコミュニケーションに関しては、現場の人の話を聞く、普段からSlackなどのチャットを観察しておく、実際にシステムを使っているところを見せてもらう、現場の人に話を聞く　というふうにしなければわからないことなので、そういう動きもしていかないと使いやすい管理画面は作れないなとここ最近強く感じています。

現場の人が使いやすいシステムを作るためにはやはり、現場の人の業務について理解を深めること、現場の人に成り切ること、現場の人の声を聞くことが重要なんだと再再々再認識くらいさせられています。

現場の人が使いやすいシステムを開発できるように、これからも研鑽、アウトプットしていきます。
ここまで記事を読んでいただきありがとうございました。
ASP.NET Core の実行時にランタイムバージョンを得る方法

# はじめに

ASP.NET Core がどのランタイムで動いているのか確認したいことがありますよね！？
`Environment.Version` を使うとランタイムのバージョン番号が得られます。。が、下記の条件付きです。

- .NET Core 3.x and .NET 5+
  - .NET runtime version number / ex. `6.0.13`
- .NET Framework Versions 4, 4.5, 4.5.1, and 4.5.2
  - `4.0.30319.xxxxx`
- .NET Framework 4.6 and later versions, and .NET Core versions before 3.0
  - `4.0.30319.42000`

https://learn.microsoft.com/en-us/dotnet/api/system.environment.version

# やってみた

下記の環境下で .NET Core 2.1 と .NET 6 の ASP.NET Core アプリで検証しました。

```
> dotnet --list-runtimes
Microsoft.AspNetCore.All 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
～省略～
Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
～省略～
Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
～省略～
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
～省略～
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
```

.NET Core 2.1 アプリの実行結果では `4.0.30319.42000` が得られました。
.NET 6 アプリの実行結果では `6.0.13` が得られました。

# おわりに

`Environment.Version` の存在は知ってたのですが、.NET Core 2.1 環境での実行結果が期待する値ではなかったので忘れてました。.NET 6 環境では問題なく利用できそうです。
Google Colabのセッション切れを防止

こんにちは。[hatter](https://twitter.com/hatterblog)です。

GoogleColab Proを使い始めましたが、セッション切れ（90分の方）に悩まされていました。
対策方法が見つかったのでメモを残しておきます。

## 対策方法
1. Google Colabを実行させるChromeブラウザでF12を押して、デベロッパーツールを開く
2. Consoleウィンドウで、次のように入力してEnterキーを押す
```
function ClickConnect(){
  console.log("60sごとに再接続");
  document.querySelector("colab-connect-button").click()
}
setInterval(ClickConnect,1000*60);
```

ひとまずこれで90分の方は解決しました。
しかし、12時間の方は対応できないので工夫が必要そうです。
お読みいただきありがとうございました。

## 参考記事
[Google Colab セッション切れを防止する](https://flat-kids.net/2020/07/28/google-colab-%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%87%E3%82%8C%E3%82%92%E9%98%B2%E6%AD%A2%E3%81%99%E3%82%8B/)
JSONに変換可能かどうかをmypyで判定する

オブジェクトがJSONに変換可能かどうかをmypyで判定したくなったことはありませんか？僕はあります。例えば、floatと間違えてnumpy.ndarrayをJSONに変換してエラーになったことがあります。

```python
>>> a = np.asarray([0])
>>> json.dumps(a)
Traceback (most recent call last):
…
TypeError: Object of type ndarray is not JSON serializable
```

こういうエラーは型で防ぎたいですが、mypyでは長らく実現不可能でした。JSONに変換可能かどうかを型で定義しようとすると再帰が出てきてしまうためです。

```python
JSON = Union[Dict[str, "JSON"], List["JSON"], Tuple["JSON", ...], str, int, float, bool, None]
```

再帰は（一部の例外を除き）mypyでサポートされていませんでした。mypy 0.981までは。

[Mypy 0.981 Released](https://mypy-lang.blogspot.com/2022/09/mypy-0981-released.html)

このリリースには "Experimental Support for General Recursive Types" が含まれ、これによりJSON型をmypyで扱うことができるようになりました。

0.981ではこの機能を使うために--enable-recursive-aliasesオプションを付ける必要がありましたが、0.991でdepreceatedになりデフォルトで再帰的な型定義ができるようになりました。

ちなみに再帰のサポートについてのissueはこれ（[Support recursive types #731](https://github.com/python/mypy/issues/731)）で、closeまで7年かかっています。開発者の皆様、お疲れ様でした。
wslでledger

# 概要
wsl(wsl2じゃない)でledgerやってみた。

# 参考にしたページ。

https://qiita.com/kikeg/items/07c2794e226b9ae18205

# インストール手順

```
$ sudo apt update

$ apt cache search ledger

$ sudo apt install ledger

$ ledger --version
Ledger 3.1.2-20160801, the command-line accounting tool
Copyright (c) 2003-2016, John Wiegley.  All rights reserved.
This program is made available under the terms of the BSD Public License.

```

# 動作確認

```
$ cd ledger
$ cat > test.ledger <<EOF
2022/12/01 スーパーで買ったわ
    Expenses:Grocery        2,000 JPY
    Assets:全財産
EOF
$ ls
test.ledger
$ cat test.ledger
2022/12/01 スーパーで買ったわ
    Expenses:Grocery        2,000 JPY
    Assets:全財産
$ ledger -f test.ledger balance
          -2,000 JPY  Assets:全財産
           2,000 JPY  Expenses:Grocery
--------------------
                   0
$ ledger -f test.ledger register
22-Dec-01 スーパーで買ったわ              Expenses:Grocery                              2,000 JPY          2,000 JPY
                                          Assets:全財産                                -2,000 JPY                  0
$ ledger -f test.ledger register "Assets:全財産"
22-Dec-01 スーパーで買ったわ              Assets:全財産                                -2,000 JPY         -2,000 JPY
$

```

以上。
テーブル設計の基礎：正規化とは

未経験からエンジニアになりたい人は、プログラミングから始める人が多いと思いますが、
エンジニアの仕事は、プログラミングだけではありません。そして、プログラミングだけでは、エンジニアになれません。同時にプログラミングが苦手でも、システム構成や開発の流れを理解するとエンジニアに近づきます！
今回はテーブル設計の基礎：正規化について更に詳しく調べてみましょう！

![インスタスライド_202301223.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23130/bdbcdaeb-a1e8-2a2c-c153-149b0b4a640b.gif)

# 正規化とは
正規化とは、効率的な表（テーブル）を設計するための方法論である。

具体的には、
・冗長性がなく
・一貫性、整合性
を保つ事が容易な設計方法

第１正規化～第５正規化までの5種類用意されている。

# 冗長性とは
・余分なもの
・余剰がある
・重複している
という意味

テーブル情報が余分、重複しているという事を意味します

# 一貫性とは
最初から最後まで、ブレずに芯が通っているという意味

データが一貫して同じデータの状態である事を意味します

# 整合性とは
2つの事柄の間にずれや矛盾がなく、ぴったり一致している意味

データの整合性が他のたれている事を意味します

# 正規化とは
正規化とは、効率的な表（テーブル）を設計するための方法論である。

具体的には、
・冗長性がなく
・一貫性、整合性
を保つ事が容易な設計方法

# まとめ

ー正規化とは、効率的な表（テーブル）を設計するための方法論

ーデータに冗長性がなく、一貫性＆整合性を保つための設計方法

ー第１正規化～第５正規化がある

# 最後に
未経験の皆さん、若手エンジニアの皆さん、勉強方法について悩みがあればなんでも気軽に質問して下さい！
これからも記事を書いていきますので、モチベーションアップのためフォロー、イイねお願いします。
日本語が使えるようにDockerfileでロケール関連の設定をする

# はじめに
DockerでMySQLを使用する際、日本語対応に手こずりました。その時にLinuxの「ロケール」というものがよくわからず混乱したので、調べてまとめたいと思います。

# ロケールとは

>In computing, a locale is a set of parameters that defines the user's language, region and any special variant preferences that the user wants to see in their user interface. Usually a locale identifier consists of at least a language code and a country/region code
[Locale (computer software) - Wikipedia](https://en.m.wikipedia.org/wiki/Locale_(computer_software))

ロケールとは言語等を設定するのに必要なもののようです。

# Dockerfileで何も設定しない場合

```Dockerfile:Dockerfile
FROM mysql:8.0-debian
```
上記のDockerfileのように、ロケールについて何も意識しないでDockerイメージを作成した場合、構築したコンテナのロケールがどのようになっているか確認します。

## ロケールの設定を確認する
localeコマンドでロケールの設定を確認できます。

```Terminal:ターミナル
# locale
```
localeコマンドで現在設定されている環境変数の一覧が表示されます。

```Terminal:ターミナル
LANG=
LANGUAGE=
LC_CTYPE="POSIX"
LC_NUMERIC="POSIX"
LC_TIME="POSIX"
LC_COLLATE="POSIX"
LC_MONETARY="POSIX"
LC_MESSAGES="POSIX"
LC_PAPER="POSIX"
LC_NAME="POSIX"
LC_ADDRESS="POSIX"
LC_TELEPHONE="POSIX"
LC_MEASUREMENT="POSIX"
LC_IDENTIFICATION="POSIX"
LC_ALL=
```
LANG、LANGUAGE、LC_ALL以外が「POSIX」に設定されています。

## 現在存在するロケールを確認する
```Terminal:ターミナル
# locale -a
```
aオプションで、現在システムの中にインストールされているロケールを確認できます。
```Terminal:ターミナル
C
C.UTF-8
POSIX
```
何もしないとロケールは3つだけのようです。

# 日本語を使用可能にするロケールの設定(Dockerfile)

```Dockerfile:Dockerfile
FROM mysql:8.0-debian

RUN apt-get update
RUN apt-get -y install locales-all

ENV LANG ja_JP.UTF-8
```
他の設定はせず、ロケールの日本語設定だけを考えたファイルです。

## ロケールのインストール
```Dockerfile:Dockerfile
RUN apt-get update
RUN apt-get install locales-all
```
ロケールを全てインストールすることで、今回使用したいと思っている「ja_JP.utf8」をダウンロードできます。
この「locales-all」でインストールすると、全部で496個のロケールがインストールされます。「ja_JP.utf8」だけインストールできないかと調べましたが、短時間では特に方法が見つけられなかったので、今回は「locales-all」で良いかなと思います。

## 環境変数の設定
```Dockerfile:Dockerfile
ENV LANG ja_JP.UTF-8
```
環境変数LANGにロケールの「ja_JP.UTF-8」を設定します。

## 日本語対応後のロケール設定
```Terminal:ターミナル
LANG=ja_JP.UTF-8
LANGUAGE=
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
```
LANGを設定することで、LANGUAGE、LC_ALL以外の全ての環境変数が「ja_JP.UTF-8」となります。
いちおうLANGを設定することでターミナル上でも日本語は使えるようになりますが、何かの機会に使うかもしれないので、LANGUAGE、LC_ALLも設定した方が良いかもしれません。


# 参考
- [ロケール（locale）まとめ - Qiita](https://qiita.com/aosho235/items/58e2e7acd5c2ee3641ff)
- [「ロケール環境変数」の種類と優先順位まとめ【LC_ALL・LC_*・LANG・LANGUAGE】 | LFI](https://linuxfan.info/locale-variables)
[Rails]既存カラムのデフォルト値を変更する方法

# 前提
ユーザー(User)テーブルに管理者フラグ(admin_flag)があるとします。
今回はデフォルトをtrueからfalseに変更してみたいと思います。
*今回はboolean型を操作しますが数値/文字列でも同様に変更可能です。

```
Table name: users

admin_flag :boolean  default(TRUE)
```

# 手順
## マイグレーションファイルを作成
デフォルト値変更のためのmigrationファイルを作成します。
今回はデフォルト値を変更するので
```Change[カラム名]DefaltTo[テーブル名]```
というマイグレーションファイル名にしています。

具体的には
```rails g migration ChangeAdminFlagDefaultToUsers```
でファイルを作成しました。

## マイグレーションファイルの中身を記述
実際に書くのは1行追加だけになります。
```ruby
def change
  # change_column_default :テーブル名, :カラム名, from: 変更前の値, to: 変更したい値
  change_column_default :users, :admin_flag, from: true, to: false
end
```
変更自体は
```ruby
# change_column_default :テーブル名, :カラム名, 変更したい値
change_column_default :users, :admin_flag, false
```
だけで可能ですが、ロールバック時にdownしなくて良いように```from/to```を指定しておいた方が楽そうです！

## マイグレーションファイル実行

```rails db:migrate```

以上でデフォルト値が変更出来たかと思います。

# 余談
### デフォルト値の設定をなくしたい場合
```ruby
# change_column_default :テーブル名, :カラム名, from: 変更前の値, to: nil
  change_column_default :users, :admin_flag, from: true, to: nil
```

### デフォルト値を追加したい場合
```ruby
# change_column_default :テーブル名, :カラム名, from: nil, to: 変更したい値
  change_column_default :users, :admin_flag, from: nil, to: false
```
2023/2/7主にITとかセキュリティの記事

苛烈なサイバー戦争の未来は不可視の〈軍拡競争〉が支配する：『サイバー戦争 終末のシナリオ』池田純一書評(2/5)
https://wired.jp/article/this-is-how-they-tell-me-the-world-ends-ikeda-review/

Oracle CloudでRHELを利用するときに気を付けること(2/4)
https://qiita.com/asahide/items/c423e6b6873c98c71ee1

[KWレポート] 超巨大AI最前線と韓国の課題 (8)(2/5)
https://www.afpbb.com/articles/-/3449983?cx_part=top_category&cx_position=1

相談件数　過去最多　サイバー犯罪被害防止を呼びかけ　広島県(2/5)
https://news.yahoo.co.jp/articles/e9f098dec388e2216cc1491d5b2b81a6b5f0602e?source=rss

あなたが取得したスキルが一目で分かる、「オープンバッジ」とは(2/6)
https://xtech.nikkei.com/atcl/nxt/column/18/01679/011100092/

公衆Wi-Fiサービスが危険な理由、「悪魔の双子攻撃」に注意(2/6)
https://xtech.nikkei.com/atcl/nxt/column/18/02353/020300002/

データ仮想化が実用期に、「コピーベースDWH」の悩み解消(2/6)
https://xtech.nikkei.com/atcl/nxt/column/18/02346/020200003/
Laravel Sanctumを触ってみる

## はじめに
SanctumのSPA認証の実装をしてみたので共有します。

## Laravel Sanctum:SPA認証
SanctumはWeb API用の認証機能を提供するパッケージです。

提供される認証方式はAPIトークン、SPA認証の2つがありますが、今回はSPA認証について解説します。

### SPA認証
SPAのためのセッション認証サービスです。
Laravelの「web」認証ガードを利用して実現しています。
CSRF保護、XSS攻撃による情報漏洩を保護します。

### SPA認証のステップ
#### 1 : セッション管理対象のフロントエンドのオリジンを指定する。
envファイルで次のようにセッション管理対象のフロントエンドのオリジンを設定します。
```.env
SANCTUM_STATEFUL_DOMAINS="localhost:80"
```
上記の環境変数は以下の箇所で使用されます。
```php:config/sanctum.php
'stateful' => explode(',', env(
    'SANCTUM_STATEFUL_DOMAINS',
    'localhost,127.0.0.1,127.0.0.1:8000,::1'
)),
```


#### 2 : CSRFトークンの取得
CSRFとは、**生成元が異なる許可されていないフロントエンドからのリクエスト送信です**。
罠が仕掛けられたリンクにアクセスすることで無関係のサイトにアクセスさせられ、利用者が意図しない操作を実行させられます。

そのため、**異なるオリジンのWebサイトからのアクセスをブロッキングする必要があります**。

CORS設定で許可したフロントエンドのみにCSRFトークンを付与し、そのトークンを持つフロントエンドからのリクエストのみを受け付けるようにします。(CORSの設定は`cors.php`で行います)

そうすることでフロントエンドの信頼性を担保しています。

Sanctumをインストールすると、自動で`/sanctum/csrf-cookie`というルートが作成されます。

```
$ php artisan route:list
 GET|HEAD   sanctum/csrf-cookie ............................................ sanctum.csrf-cookie › Laravel\Sanctum › CsrfCookieController@show
```

こちらにアクセスすると、レスポンスのcookieに`XSRF-TOKEN`が入って返ってきます。

このcookieに入ってるトークン(CSRFトークン)をフロント側で`X-XSRF-TOKEN`ヘッダにセットすることでリクエストを送信することができるようになります。

#### 3 : Sanctumのミドルウェアを使用する
Sanctumのミドルウェアを`app/Http/Kernel.php`ファイル内のapiミドルウェアグループに追加します。
```php:app/Http/Kernel.php
protected $middlewareGroups = [
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];
```
こうすることで、`api.php`ファイル内のルートに適用したいミドルウェアを指定できます。

`EnsureFrontEndRequestsAreStateful`ミドルウェアはセッションの作成、CSRFトークンが正常であるかなどを確認します。

`SANCTUM_STATEFUL_DOMAINS`に設定されているドメインからのアクセスの場合にトリガーされます。

問題がない場合は新しいセッションが作成され、対応するcookieが返されます。

#### 4 : ルートを保護する
認証済みじゃ無いとアクセスできないルートは次のように定義できます。
```php:routes/api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});
```
`/api/user`にアクセスする際に、認証済みで無い場合はエラーになります。

Sanctumの設定は以上になります。

※ログイン成功時にセッションを作成する箇所は別途実装する必要があります。(Laravel Fortifyを利用すれば最初からセッションを作成するコードは書かれているため実装は不要です。)
### まとめ

Sanctumがやってくれることは大きく分けて次のことになります。

・CSRFトークンを提供するAPI
・セッションの作成
・認証済みであることを条件とするルートの作成

新規登録、ログイン、ログアウトなどの機能は別途実装する必要があります。

### 参考文献
Laravel Sanctum

https://readouble.com/laravel/8.x/ja/sanctum.html

https://dev.to/nicolus/laravel-sanctum-explained-spa-authentication-45g1

CSRF

https://yamory.io/blog/about-csrf/

CORS

https://www.tohoho-web.com/ex/cors.html
Nuxt3でDayJSを使用する

上手く動くのに時間がかかったのでメモ
### Day.jsをインストール
```
npm i dayjs
```
### pluginsの作成
`plugins/`以下に`dayjs.ts`を新規作成する：

```ts
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime.js'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
    dayjs.extend(relativeTime)
    nuxtApp.provide('dayjs', dayjs)
})

declare module '#app' {
    interface NuxtApp {
        dayjs: dayjs.Dayjs
    }
}
declare module '@vue/runtime-core' {
    interface ComponentCustomProperties {
        dayjs(date?: dayjs.ConfigType): dayjs.Dayjs
    }
}
```

呼び出すときは以下のようにする：
```ts
const nuxtApp = useNuxtApp();
const dt = nuxtApp.$dayjs().format('YYYY-MM-DD')
```

- もっとスマートなやり方もあるような。
- `$dayjs`の`$`はおまじない(死語)だと思っています。

### 参考
https://kas.kim/blog/using-dayjs-in-nuxt-3-as-plugin/
【Flutter】Firebaseと連携時にエラー

# 初めに
Flutterで個人的に開発途中、Firebaseと連携しアプリをビルド
したところエラーが発生。。

ちょっとみたこと無いエラーだったので
備忘録として記録。

## エラー内容
どうやらappフォルダ直下のbuild.gradleに何かありそう。。

```
FAILURE: Build failed with an exception.

* Where:
Build file '~/flutter_sample/android/app/build.gradle' line: 29

* What went wrong:
A problem occurred evaluating project ':app'.
> Could not find method id() for arguments [com.google.gms.google-services] on project ':app' of type org.gradle.api.Project.
```
場所を教えてくれている。。
素直にbuild.gradleを確認。。

```app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
id 'com.google.gms.google-services' // ←これについて何か言ってる
```

うーん、直感ではこの「id」という書き方があかん感じ。。
仕方ない、FlutterとFirebaseの連携について
ググって情報収集しよう。。

そんな中build.gradleについてこんな記載が。。

```app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services' // ← 「id」じゃない！？
```
どうやらidではなく「apply plugin」という記載が正しいっぽいので
同じ内容に修正。。

再度ビルドしたら別のエラーが発生。。
```
.gradle/caches/transforms-2/files-2.1/95d7102c3bcd9bd5e3f48b4a45f773e4/jetified-firebase-analytics-ktx-20.0.2-api.jar!/META-INF/java.com.google.android.libraries.firebase.firebase_analytics_ktx_granule.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.5.1, expected version is 1.1.15.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:compileDebugKotlin'.
> Compilation error. See log for more details
```

うわなにこれ。。
と諦めかけていたその時、「The binary version of its metadata is 1.5.1」
という記載が目に入る。。

どうやらKotlinのなにかしらのモジュールに関するバージョンが
誤っているっぽい。。

そこでググった情報を基に、android直下の方のbuild.gradleを
修正。

### 修正前
```build.gradle
ext.kotlin_version = '1.3.50'
```

### 修正後
```build.gradle
ext.kotlin_version = '1.6.0'
```
:::note warn
「ext.kotlin_version」　についてはまだ調べられておらず。。
何かご存知な方いらっしゃったら教えてください！
:::

上記対応後、無事に動きました！
FirebaseやAndroidプロジェクトについてまだまだ
勉強不足ですね。。w

## 参考文献

https://stackoverflow.com/questions/67699823/module-was-compiled-with-an-incompatible-version-of-kotlin-the-binary-version-o
node.jsインストール（nvm for Windows）

# 前提

Windowsにnode.jsをインストールする。
nvmを使ってインストールすることでnode.jsのバージョンを切り替えられるようにする。

現時点、node.jsのバージョンは16.xを使うこととする。
理由 : AWS Lambdaでサポートするランタイムが `Node.js 16` までだから。

マイナーバージョンはプロジェクトで利用の関係上、後述のバージョンとする。

## 入手

- https://github.com/coreybutler/nvm-windows
- nvm-setup.exe

## インストール

インストーラファイルを実行して以下の画面のように設定を進める。

![nodejsinstall01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224453/0f21430b-7fc7-9787-6961-a3010c045af9.png)

![nodejsinstall02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224453/6340cdcb-a385-d358-e8c9-5f76f7bfde51.png)

![nodejsinstall03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224453/f84f2cf2-6a0d-7815-941b-eeb678ddfc6a.png)

![nodejsinstall04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224453/f309c548-a03c-88f9-57b0-7ea673ade80e.png)

![nodejsinstall05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224453/f4d0bc7b-83a2-eaa4-c710-3e46a3116b80.png)


## node 16.16.0インストール

※管理ユーザー権限で実行すること

```
> nvm install 16.16.0
Downloading node.js version 16.16.0 (64-bit)... 
Extracting...
Complete


Installation complete. If you want to use this version, type

nvm use 16.16.0
```

インストールしたバージョンを確認

```
> nvm list

    16.16.0
```

## node 利用バージョン指定

```
> nvm use 16.16.0
Now using node v16.16.0 (64-bit)
```

## バージョン確認

```
> node --version
v16.16.0
> npm --version
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
8.11.0
```

※npmでglobalインストールすると--localtion=global推奨メッセージが出る（アップデートすると解消する）

## npmバージョンアップ

```
> npm -g update
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.

changed 30 packages, and audited 204 packages in 6s

found 0 vulnerabilities
```
YouTubeDataAPIを利用して指定のチャンネルの最新情報やライブ中かどうかを取得する(ためのURL)

前提：YouTubeDataAPIを使える(APIキーを取得している)こと

YouTubeDataAPIはGETするだけなのでURLだけ以下に示す。
言語が何であれGET方式である以上、やるべきことはURLの作成にすぎない。

指定のチャンネルがライブ中かどうか取得：
https://www.googleapis.com/youtube/v3/search?key=[APIキー]&part=snippet&channelId=[チャンネルID]&eventType=live&type=video

これをプログラムで投げる。
結果はJSONで返ってくるので、中を見れば欲しい情報がどこにあるかは分かるはず。

チャンネルID(UCから始まるやつ)が不明なら、HTMLのitemprop="channelId"のあたりを調べればいい。
※ブラウザからページのソースを表示で確認できる

取得したチャンネルIDが正しいかは以下で確認。
https://www.youtube.com/channel/[チャンネルID]

コーディングする前に手動でURLを作成し、ブラウザで投げれば動作確認できる。
※パラメータの仕様確認だけなら以下のリファレンスから可能
https://developers.google.com/youtube/v3/docs?hl=ja

APIの制限(クオータ)でsearchだと1日100回程度まで。
クオータを節約したいなら事前にYouTubeのRSSを調べるとよい。

YouTubeの指定のチャンネルのRSS：
https://www.youtube.com/feeds/videos.xml?channel_id=[チャンネルID]

方針として、YouTubeの規約でスクレイピング(というより自動的な問い合わせ)が禁止されているので、
RSSでクオータを節約しつつ、更新があればAPIで問い合わせする感じ。
【ServerlessFramework】template の API Gateway + lambda 構成で CORSにハマった

# はじめに

普段はフロント側を`typescript`で書き、lambdaは`ruby`や`nodejs`で書いている私ですが、
<font color="red">そろそろバックエンドもtypescriptで書きたい！</font>と思い、
`sls create -t aws-nodejs-typescript`で`typescript`環境を作ってみた際、
CORSエラーにハマった時のメモ。

# 前提

* ServerlessFramework
    * Framework Core: 3.27.0 (local) 3.27.0 (global)
    * Plugin: 6.2.3
    * SDK: 4.3.2
* typescript
    * Version 4.9.5
* ts-node -v
    * v10.9.1

# 構築手順

1. テンプレートから作成

    ```zsh
    $ sls create -t aws-nodejs-typescript -p hoge
    ```
    -pは省略可能(無い場合はcurrent directry

1. デプロイ
    
    ```zsh
    $ sls deploy
    ```

無事デプロイが完了すると

```zsh
Deploying hoge to stage dev (us-east-1)

✔ Service deployed to stack hoge-dev (124s)

endpoint: POST - https://hogehoge.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
  hello: hoge-dev-hello (12 kB)
```

のように`endpoint`が表示されるはず。

試しにPOSTMANや、VScodeの拡張機能である、ThunderClinetで表示されたendpointへPOSTを送ってみる

```:request
method: "POST"
Headers: { Content-type: application/json }
Body: { "name": "hoge" }
```

すると、、、

![スクリーンショット 2023-02-07 0.40.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/61f7a988-a4a3-41aa-2562-8d8104dc4ae6.png)


ちゃんと`Hello hoge, ....`とmessageが帰ってきた！

API Gateway + lambdaの構成は作れた！

# 事象発生

次にウェブサイトからjavascriptの`fetch`を使ってPOSTを送ってみたところ、、

![スクリーンショット 2023-02-07 0.45.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/fa8fd02e-404c-950a-3e4d-ede4055b079b.png)

<font color="red">CORSエラー発生</font>


# 解決

`./src/libs/api-gateway.ts`にあるレスポンス生成部分に`Access-Control-Allow-Origin`の設定を追加

```typescript:./src/libs/api-gateway.ts
export const formatJSONResponse = (response: Record<string, unknown>) => {
  return {
    statusCode: 200,
    headers: {                              // 追加
      'Access-Control-Allow-Origin': '*',   // 追加
    },                                      // 追加
    body: JSON.stringify(response)
  }
}
```
articleの使い方を復習します

## article

<blockquote>
article要素は、文書、ページ、アプリケーション、またはサイトの中で完全もしくは自己完結した構造を表す。これは原則として、たとえばシンジケーションなど、独立して配布可能なまたは再利用可能なものである。これは、フォーラムの投稿、雑誌や新聞の記事、ブログのエントリー、ユーザーの投稿コメント、対話的なウィジェットやガジェット、またはコンテンツの任意の独立した項目であるかもしれない。
article要素がネストされる場合、原則的に内側のarticle要素が外側の記事の内容に関連する記事を表す。たとえば、ユーザーの投稿コメントを受け付けるサイトのブログのエントリーは、ブログのエントリーのarticle要素内にネストされたarticle要素としてコメントを表すことができる。
article要素に関連付けられている著者の情報（address要素を参照のこと）は、ネストされたarticle要素には適用されない。

</blockquote>

サンプルコード
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>記事タイトル(ページタイトルとして)</title>
</head>
<body>
    <header>
        <nav>ナビゲーション</nav>
    </header>
    <article>
        <h1>article用の記事タイトル</h1>
        <p>記事内容</p>
        <section>
            <h2>section用の見出し</h2>
            <p>見出し2の内容</p>
        </section>
    </article>
</body>
</html>
```
- sectionよりも具体的である
- 見出しが必要
- ブログ投稿などがイメージしやす

### articleの中にarticle

>article要素がネストされる場合、原則的に内側のarticle要素が外側の記事の内容に関連する記事を表す。たとえば、ユーザーの投稿コメントを受け付けるサイトのブログのエントリーは、ブログのエントリーのarticle要素内にネストされたarticle要素としてコメントを表すことができる。

サンプルコード
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>記事タイトル(ページタイトルとして)</title>
</head>
<body>
    <header>
        <nav>ナビゲーション</nav>
    </header>
    <article>
        <h1>article用の記事タイトル</h1>
        <p>記事内容</p>
        <section>
            <h2>section用の見出し</h2>
            <p>見出し2の内容</p>
        </section>
        <article>
            <header>
                <h2>コメントのタイトル</h1>
                <p>3日前</p>
            </header>
            <p>ネストする場合、親articleに関連したもの</p>
        </article>
    </article>
</body>
</html>
```

## article section div　の使い分けフロー

![スクリーンショット 2023-02-07 0.29.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/aeb01626-b4b9-eb63-0402-b238e4226991.png)

参考
- https://html.spec.whatwg.org/
- https://momdo.github.io/html/
- https://developer.mozilla.org/ja/docs/Web/HTML/Content_categories
- [4.3.2 article要素](https://momdo.github.io/html/sections.html#the-article-element)
- [divとsectionとarticle要素それぞれの特徴、使い分けと組み合わせ方 -HTML5のセクショニング コンテンツ](https://coliss.com/articles/build-websites/operation/work/sectioning-content-in-html5-by-ire.html)
オフラインでIISをインストールしたときにエラー

<h1>はじめに</h1>
オフラインでIISをインストールしたときに以下のエラーが出ました。


>指定されたサーバーで機能を追加または削除する要求に失敗しました。<br>1つ以上の役割、枠割サービス、または機能をインストールできませんでした。エラー:0x8024402



<h1>環境</h1>

- Windows Server 2019 評価版
- Hyper-V仮想環境を構築

<h1>結論</h1>
アプリケーション開発 > .NET 拡張機能 3.5 と ASP.NET3.5 のチェックを外したらオフラインでインストール出来ました。
<br>
.NET 拡張機能 3.5をインストールしたい場合、ISOファイルをダウンロードする必要があるようです。
    <br><br>

[FrameWork3.5のオフラインセットアップにて、エラー：0x8024402C](https://answers.microsoft.com/ja-jp/windows/forum/all/framework35%E3%81%AE%E3%82%AA%E3%83%95%E3%83%A9/5cf74131-ac05-4310-85b7-449a762cdc3c "Qiita Home")

<br>
念のためここまでに至った手順を掲載します。
<h1>手順</h1>
コントロールパネル > プログラム > Windowsの機能の有効化または無効化

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/a0afc70e-88ef-31da-266d-552d4e0246d1.png)

サーバーの役割 > Webサーバ-(IIS)にチェック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/0f5b6c66-61f6-0601-9efd-182be734e777.png)

以下のように選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/eb2fdea3-20be-6c7f-6f4c-73deefef2242.png)

インストールを開始したところ、エラー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/97b69cd4-0e8d-0008-5a7c-6c9dedaaf206.png)

「.NET 拡張機能 3.5」と「ASP.NET 3.5」を外して再トライしたらインストールに成功！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/a510a4ed-7622-dfbe-af86-1ccb9a6c5d80.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/75300d56-a472-9215-02c7-f9bbe4e09ac0.png)

localhostでiisのこの画面が表示されました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610435/d7b93dc8-96e4-7b53-da81-68d46fd30469.png)
SQLで可変的アンピポット方法（postgresSQLのみ）


https://twitter.com/tako_and_kitune/status/1622222483159130112?s=20&t=ukL209eG1kfZhmezRj6zZQ

テーブル上に、項目列数を持つ必要があるけれど、
可変的なアンピポット方法ができたので、備忘録として追加
（PowerQuery、Pythonとかのが楽とかある気がする。）

今回は、金額と税額、また対象の科目になっているものを除外するように作成。
実際はここまでテーブル数は必要ないと思う


```sql
--多分、postgres限定のクエリ
with tmp_2 As ( 
select 
    null
    , おいしみ.*
    , null
from おいしみ
)
,tmp AS (
   --select結果を配列に格納
   -- Array[項目A,項目B]が正しいが、動的取得のためジョグ配列っぽく取得
    select array[tmp_2.*] As arr  from tmp_2
    )
    , 配列加工 As (
    -- ジョグ配列を普通の配列に加工
    select string_to_array(arr[1]::text,','::text,'') AS おいしみarr from tmp
     )
     , 添え字 As (
    -- index作成
    select 
      おいしみp.名称
    , おいしみp.列数 As 科目idx
    , おいしみp.列数 + おいしみc.id As idx
    , おいしみp.名称 || おいしみc.名称 As col名称
       from おいしみparameter As おいしみp, おいしみcol As おいしみc
       where おいしみc.計算有無 = True
       
    ) 
    , unpivot As (
    -- unpivotの内容を取得
    select 配列加工.おいしみarr[1+1] As 日付
    , 添え字.名称 As 〇方 
    , おいしみarr[添え字.科目idx+1] As 科目
    , 添え字.col名称 , おいしみarr[添え字.idx+1]::integer  As 金額 from 配列加工, 添え字
 
    )
    -- 集計
    select unpivot.日付 , unpivot.〇方 , unpivot.科目
    , sum(unpivot.金額) As 集計金額
     from unpivot 
     where   unpivot.科目 NOT LIKE '%_対象外'
    group by unpivot.日付 , unpivot.〇方,  unpivot.科目

```
SQLでの反復出力（postgresSQL）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319265/1e7e4399-b146-6b93-3413-e5a3dfe1c4c9.png)

エンジニア騎士とクエリの魔女への私なりの回答です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319265/b0c65325-7c00-848a-0047-4a90a2c2f508.png)

```sql
WITH arg AS (
    SELECT 
         @param AS param
) 
, seq AS ( 
    SELECT
          generate_series(1, arg.param ::INTEGER) AS seq 
    FROM
        arg
) 
SELECT
      string_agg('hello world', ',') 
FROM
    seq

```

※この後、repeat(text,integer)で解決デキたのでは？と思いました
【Git】upstreamに向けて出してある他の人のPRを自分のローカルでcheckoutする方法

他の人が出したPRを自分のローカルに持ってきてcheckoutして確認する方法。
```
$ git fetch upstream pull/{プルリクエストのID}/head:{ブランチ名}
```
こんな感じ。 
間違ってたら教えてください。
chroot、DNSSEC、TSIG、DANEについて

DNSサーバのセキュリティを向上させる手段の一例
chroot、DNSSEC、TSIG、DANE

●chroot
あるプロセスのルートディレクトリを、指定したディレクトリ( /chrootディレクトリ等）に変更する操作。
これにより、例えBINDを不正に操作されたとしても、変更後のディレクトリ内のファイルしか操作できなくなる為、被害を最小限に抑える事ができる。

namedをchroot環境で動作させる為には、オプションを付加して起動させる必要がある。

namedのオプション
・-t　namedのルートディレクトリとするディレクトリを指定
・-u　namedの実行ユーザを指定
・-g　namedの実行グループを指定

●DNSSEC（DNS Security Extensions）
公開鍵暗号方式を用いた電子署名により、正当なDNSサーバからのゾーン情報である事を認証し、またゾーン情報が改竄されていないことを保証する技術。

DNSSECでは二種類の鍵を用います。
・ゾーンの署名鍵（ZSK：Zone Signing Key）... ゾーンに署名する
・鍵の署名鍵（KSK： Key Signing Key）... ゾーンに署名した鍵に署名する
それぞれは公開鍵と秘密鍵のペアになっている。

■公開鍵と秘密鍵を作成するには dnssec-keygen コマンドを使用し、ゾーンファイルへの署名に dnssec-signzone コマンドを使用。

dnssec-keygenコマンドの書式

dnssec-keygen [オプション] 鍵の名前（※DNSSECの場合はZone名）

▼主なオプション
・-a　暗号化のアルゴリズムを指定(デフォルトはRSASHA1)
・-b　鍵のビット長を指定
・-n　鍵のオーナータイプを指定

ファイル名の末尾が「.key」となっているのが公開鍵が格納されているファイルで、「.private」となっているのがこれと対になる秘密鍵が格納されているファイル。

●TSIG（Transaction Signature）
共通の秘密鍵を設定する事で、送信元のなりすましや更新データの改ざんを防止するための技術で、ゾーン転送でのDNSサーバの偽装防止、不正なクライアントからのrndcコマンドの操作防止などに使われます。
TSIGが正しく動作するには、送信元（クライアント）と送信先（サーバ）の時刻があっていることが必要。TSIGは秘密鍵を用いてDNSメッセージ全体の署名を行うが、その際に時刻情報を埋め込みます。この署名を受け取った側が署名を検証する際に、許容される範囲以上に時刻がずれていると検証結果が「不正」とみなされてしまうため。

※TSIGもDNSSECと同様にゾーン情報自体の暗号化は行わない

●DANE（DNS-based Authentication of Named Entities）
DNSSECの技術を応用して認証にDNSを使う仕組み。
DNSSECにより、リソースレコードの正当性確認の仕組みがDNSに組み込まれたため、HTTPSなどで使われるX509の証明書とドメインの紐づけの役割を、認証局からDNSに移すことを目的にし、策定されている。
デジタル署名されたレコードをTLSAレコードといい、webのHTTPSで使用されるTLSと技術的には同じ方法を用いて、信頼の基盤を認証局からDNSSECに変更します。TLSAはTLS Associationの略で、サーバ認証に使われる証明書や鍵の情報がドメイン名に対して関連付けられている。
nvimでTailwindCSSの色コードを高速にプレビュー

nvimでCSSなどの色コードを高速にプレビュー可能なpluginとして[norcalli/nvim-colorizer.lua](https://github.com/norcalli/nvim-colorizer.lua)があり、利用しているnvimユーザーも多いかと思います。

しかし、メンテナンスが3年前で止まっていて、その後流行したTailwindCSSの色表示がサポートされないなどの課題があります。

そこで登場したのが上記pluginをforkした[brenoprata10/nvim-highlight-colors](https://github.com/brenoprata10/nvim-highlight-colors)です。

## セットアップ

Plugでの導入例です。この順序で書かないと動作しません。

```vim
call plug#begin()
Plug 'brenoprata10/nvim-highlight-colors'
call plug#end()
:
略
:
set termguicolors
lua require('nvim-highlight-colors').setup {
    \ 	enable_tailwind = true
    \ }
```

## TailwindCSS用の設定

### `enable_tailwind = false`

`red`の部分だけ赤になっていて変です。

![false.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15414/97e54f87-99b1-034c-31f7-afe4f96bb876.png)

### `enable_tailwind = true`

`red`の部分も正しく表示されています。

![true.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15414/13357103-bbb3-60be-5a1e-32abfb2742ee.png)


## 注意

TailwindCSSベースのUIコンポーネントライブラリdaisyUIのbase関連の色などはこのpluginでも表示されません。
YouTubeDataAPIのAPIキーを取得するまでの手順

【手順0】．Googleアカウントでログインしておく。

【手順1】．Google Developer Consoleにアクセスする。
Google Developer Consoleと検索すれば出てくる。

【手順2】．APIライブラリよりYouTubeDataAPIを選択し有効化する。
検索してボタンを押すだけ。

【手順3】．ページの上の方にあるプルダウンメニューからプロジェクトを選択する。
初期のやつでいい。嫌なら新しく作る。
※プロジェクトが選択されていないと次手順のAPIキー作成画面に行けないので注意。

【手順4】．認証情報より認証情報を作成でAPIキーをクリックして作成する。
ボタンを押すだけ。APIキーは1つあればいい。

以上(どこに何があるか知っていれば1分で終わる)
【Windows, Mac】address already in use :3000 対応 「プロセスを見つけて強制終了する」を1コマンドで




# はじめに

プロセスのIDを取得して、それをkillするのを1つにまとめている形です。
強制終了させるコマンドなので、用法用量にはご注意願います。

# Windows(コマンドプロンプト)

```windows(コマンドプロンプト)
for /f "tokens=5" %a in ('netstat -ano ^| findstr :3000') do taskkill /F /PID %a
```

# ついでに powershell

```powershell
foreach ($processId in (Get-NetTCPConnection -LocalPort 3000).OwningProcess) {
    taskkill /pid $processId /f
}
```

# Mac (といっていいのか...）

```mac
lsof -i tcp:3000 | awk 'NR>1 {print $2}' | xargs kill
```
【React Router v6】コンポーネントを跨いで<route>と<Link>を設置するとエラーが出る【初学者向け】

## はじめに
react router を使ってルーティングする際、込み入ったデザインだとコンポーネントを跨いでタグを配置することになります。
自分はそこで少し躓いてしまったので、誰かの役に立つのではと思い記事に残してみました。
## まず、\<Link>を直接\<BrowserRouter>の中に書くことができる場合
```JavaScript:App.tsx
import { Sidemenu } from "components/organisms/Sidemenu";
import { BrowserRouter,	Routes,	Route, Link } from 'react-router-dom';
function App() {
    return (
        <Sidemenu />
        <main>
            <BrowserRouter>
                <Routes>
                    <Route path="/" element={<Top />} />
                    <Route path="SubPage" element={<Subpage />} />
                </Routes>
                <Link to="/">
                    トップページ
                </Link>
        		<Link to="/SubPage">
        			サブページ
        		</Link>
            </BrowserRouter>
        </main>
    );
}
```
このパターンなら、各サイトに似た構成の解説が載っているため、初心者の私たちでも実装できます。

ただここで上司から、
\<Sidemenu />コンポーネント内に\<Link>を移動させてほしい
という指示が飛んできたとしましょう。

## \<Link>が別コンポーネントにある場合
```JavaScript:App.tsx
import { Sidemenu } from "components/organisms/Sidemenu";
import { BrowserRouter,	Routes,	Route } from 'react-router-dom';
function App() {
    return (
        <Sidemenu />
        <main>
            <BrowserRouter>
                <Routes>
                    <Route path="/" element={<Top />} />
                    <Route path="SubPage" element={<Subpage />} />
                </Routes>
            </BrowserRouter>
        </main>
    );
};
```
```JavaScript:Sidemenu.tsx
import { Link } from 'react-router-dom';
export const Sidemenu = () => {
	return(
		<Link to="/">
			トップページ
		</Link>
		<Link to="/SubPage">
			サブページ
		</Link>
	);
}
```
**はい、もう動きませんね😢**

ちなみにエラーはこんなかんじ
```
Uncaught Error: useHref() may be used only in the context of a <Router> component.
```
とか
```
The above error occurred in the <Link> component:
```
など

## ただこれだけのことだった
```JavaScript:App.tsx
import { Sidemenu } from "components/organisms/Sidemenu";
import { BrowserRouter,	Routes,	Route } from 'react-router-dom';
function App() {
return (
    <BrowserRouter>
        <Sidemenu />
        <main>
            <Routes>
                <Route path="/" element={<Top />} />
                <Route path="SubPage" element={<Subpage />} />
            </Routes>
        </main>
    </BrowserRouter>
);
};
```
#### ルーティングに関わる要素を\<BrowserRouter>で囲めば済む話でした

ついつい初心者の私たちは間違った固定概念を持ってしまいがちです。
今回の場合でいうところの
😠「**\<BrowserRouter>の直下には\<Routes>が存在しないといけないんだ！**」
という思い込みですね。
~~↑さっきまでの私~~

自分はこれを解決するのに１時間以上の時間をかけてしまいました。
意外とこのシチュエーションの記事は無かったので、
<font color="#DDD">（初歩のミス過ぎてみんな書かないだけかも）</font>
もしよろしければ参考にしてみてください。

※なにか間違っている部分があればご教示いただけると幸いです。
シーケンス図とは

## はじめに
現場でシーケンス図を書く機会があったのでメモとして残しておきます。

## シーケンス図とは
シーケンス図とは「プログラムの処理の流れや概要」を設計する際に使われます。オブジェクト指向のソフトウェア設計においては、グローバルスタンダードの設計手法で、オブジェクト間の処理の流れを時系列で表していく図になります。

## シーケンス図の書き方
シーケンス図は、先ほども説明した通り「プログラムの流れ」をわかりやすく図に表していくものです。なざっくり言うと、ユーザーがこの操作をしたら、この画面からこのメソッドが動いて、そのメソッドに基づいたデータが取得できて、画面に表示されるといった流れを図に表していきます。
シーケンス図にもいろんな書き方がありますが、今回は下記のシーケンス図を参考にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/a5fe2130-3a6a-dc9b-7aa1-4f680a8eb22d.png)
この図のプログラムの流れを説明していきます。

#### 1.ブラウザの操作
この画像では、最初にユーザーがブラウザで商品検索をした想定です。
#### 2.サーバーから商品を取得するメソッドが動く
商品を検索するためのメソッドが動き、サーバーがDBから商品情報を取得しにいく。
#### 3.DBから検索結果を取得
先ほど動かしたメソッドによる取得結果を受け取る。
#### 4.関連商品情報を外部システムから取得
こちらのシステムでは、外部システムからデータを取得するメソッドがあるみたい。
そのメソッドが動き、外部システムからデータを取得しにいく。
#### 5.関連商品情報を取得
先ほど取得しにいったデータをサーバーが受け取る。
#### 6.検索結果を表示
DBと外部システムから取得した結果をブラウザ(ユーザー)に返して、画面に表示する。

以上がこの画像のシーケンス図の流れになります。

## シーケンス図で使う記号など
シーケンス図でよく使う記号は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/ce4a1ea8-f4b0-5f4c-0c26-3e92343ab2bd.png)



## 補足
・先ほど説明したシーケンス図は、送られるデータの詳細は書かれていませんでしたが、データを取りにいくときに必要なデータを書き込むこともあります。
例：ユーザーID、商品ID、商品名など


以上になります。
いつの間にかDBが破損して動かない

### いつの間にかmysqlのデータが破損して動かなかった
めちゃ萎えました
Localhostを開くと
```
General error: 1812 Tablespace is missing for table
```
的なエラーで表示されない！！

とりあえず開きたいDBが破損していたので
破損しているものをDrop（GUIでドロップしたのでコマンドはわかりません。）

そして
```
php artisan migrate
```
すると、さらなるエラーが
```
Base table or view already exists:
```
既にテーブルありまっせ的なエラー
いやないやんけと思いながらググると
```
php artisan migrate:refresh
```
で解消と書かれているのでやってみるとまたエラー
そこでこの記事に出会いました
https://qiita.com/nozomi_nozomi/items/e355d8067d32603318b9
tinkerを使ってDBのテーブルを検索、削除しています、、、

でやり直したいテーブルまでロールバックして、
とどめの
```
php artisan migrate
```
seederのデータも消えているので再度seedします
よかったです。
ありがとうございました。
dockerによる機械学習環境構築(PyTorch編)

# 0.はじめに
この記事は私が機械学習で利用する環境のdocker imageを共有するために書きました。
機械学習をしたいけど環境構築が難しくてできないという方の役に立てば幸いです。

深層学習フレーム
- pytorch

データフレーム操作
- pandas
- polars
- cudf

Dockerfileなどはgithub([kaggle_pytorch_docker](https://github.com/oriki101/kaggle_pytorch_docker))に公開しています。

## 1. Preparation
機械学習用の環境を構築するためにまずdockerをインストールしてください。  
dockerを利用できる環境の作り方がわからないという方は[NVIDIA Dockerって今どうなってるの？](https://medium.com/nvidiajapan/nvidia-docker-%E3%81%A3%E3%81%A6%E4%BB%8A%E3%81%A9%E3%81%86%E3%81%AA%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%AE-20-09-%E7%89%88-558fae883f44)を参考にしてください。

## 2. Build Image
1.を基にdocker環境が構築できましたら、機械学習用のdocker imageをビルドしましょう！  
以下のコマンドを実行してください。
```
$ git clone https://github.com/oriki101/kaggle_pytorch_docker.git #docker imageを作成するためのDockerfileを保存したリポジトリ
$ cd kaggle_pytorch_docker
$ ./build.sh
```
もし実行権限がないようでしたらchmod等で権限を適切に付与してください。  
こちらの処理にはものすごく時間がかかります。

## 3. Using docker image
dockerコマンドとdocker-composeコマンドを利用した環境の建て方を用意しています。  
現状の構成ではどちらを利用していただいても大丈夫です。  
もし複数のコンテナを立てたいという場合はdocker-composeのものを参考に、改良してください。

### dockerコマンドでの起動
```
$ ./run.sh
```
### docker-composeコマンドでの起動
```
$ ./compose_up.sh
```

### 機械学習環境へのアクセス
上記のコマンドを実行すると機械学習環境が起動されます。
現在操作しているPCで実行した場合は  
localhost:8888  
にアクセスしてください。  

リモートPCで環境を起動した場合は  
実行したPCのIPアドレス:8888  
にアクセスしてください。

## 4. Explanation of environment
機械学習環境にアクセスすると下の画像のような画面になると思います。

![view.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/71b6ec0a-bb61-3bda-5fd0-6613ea45b7b8.png)

### Python3
Notebook欄のPython3は.ipynbのファイルを生成し、インタラクティブにスクリプトを描くことができます。

### desktop
真ん中のdesktopはnovncでGUI操作できるデスクトップを作成してくれます。  
ボタンをクリックすると新しいタブができ、少し時間が経つと下図のようなデスクトップ画面が開きます。  
強化学習などでGUIアプリを起動したい場合に利用してください。

![novnc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/ca6d6128-00a8-f029-45af-33d9df49fbc6.png)
### VS Code
一番右のVS CodeはVisual Studio Codeの環境を開いてくれます。  
.pyファイルやconfigファイルなどを記述する際は利用してみてください。

# 5.おわりに
今回共有したdocker imageで大抵のことができると思います。  
このdocker imageが機械学習の勉強の役に立ってくれるととても嬉しいです。  
何らかのパッケージやpytorchのバージョンを上げたものを作って欲しいという要望などがありましたら、issueであげてもらえると時間がある時に改修しようと思います。　　　
最後まで読んでいただきありがとうございました。
例外処理

# 例外処理とは
例外というのは、エラー（ランタイムエラー）のことです。エラーが出るとそこでプログラムは強制終了してしまいます。
しかし、外部との通信やデータのやり取りをするようなプログラムは様々な要因によるエラーを避けることはできません。
そこで、JavaScriptやTypeScriptには、エラーを取り扱うための方法が用意されています。

# throw文とErrorオブジェクト
throwというのは、「投げる」という意味です。throwで何かが投げられるとエラーが発生します。

```ts
const dog = "Hello";

throw dog;
    
console.log("world");
```
この例では、throwで変数dogを投げています。この時点でエラーが発生するのでプログラムは終了してconsole.logまで到達することはできません。throwに渡すのは、式なら何でも可能です。

そして、throwに渡すのは基本的にErrorオブジェクトを指定します。まずは、newを使ってErrorインスタンスを作成します。
Errorコンストラクタは、引数に文字列を指定することができ、その文字列がエラーメッセージとして扱われます。Errorインスタンスにはmessageプロパティがあり、これがエラーメッセージを表しています。なのでコンストラクタで文字列を指定してあげます。

```ts
const err = new Error("エラーが発生！！");

throw err;
```
現状だとエラーが発生したら強制的にそこでプログラムが止まってしまうのですが、そこで、エラーが発生してもプログラムを実行し続けるための機能が用意されています。

# try-catch文
エラーが発生してもプログラムが強制終了しないようにするためには、try-catch文を使用します。最も基本的な形は次のように書きます。

```ts
try {
// tryブロック（文を複数記述できる）
} catch(err) {
// catchブロック（ここも文を記述できる）
}
```
このように2つのブロックからなり、try-catch文では、必ず{ }でブロックを用いなければいけません。catch (err)の部分に出てきているerrは変数名なので好きな名前を使用できます。

まずtryブロックの中の処理を実行し、tryブロックの中で例外が発生した場合にはcatchブロックを実行します。このとき、tyrブロックの処理は中断され、例外が発生した時点で即座にcatchブロックに移行します。catchというのは、throw（投げた）エラーを受け止めるというイメージです。throwによって投げられたErrorオブジェクトがcatchで宣言された変数errに代入されます。

```ts
try {
    console.log("エラー発生させる");
    throw new Error("エラー");
} catch (err) {
    console.log("エラーキャッチした");
    console.log(err);
}

console.log("終了");
```
これを実行すると、エラー発生させるという文字列とともにエラーをキャッチしたという文字列とエラー、終了と表示されるはずです。try-catch文を使うことでエラーが発生してもプログラムを中断することなく最後まで実行することができました。

ちなみに、今回はthrowで強制的にエラーを発生させましたが、throw以外の要因でエラーが発生する場合でもtry-catch文を使うことができます。次の例では変数bは存在しないでのエラーが発生しています。

```ts
try {
    console.log("エラー発生させる");
    console.log(b);
} catch (err) {
    console.log("エラーキャッチした");
    console.log(err);
}

console.log("終了");
```

# finally
try-catch文の後ろにはさらにfinallyブロックを付け加えることができます。また、catchブロックを省略してtry-finallyという形にすることもできます。
```ts
try {
    console.log("tryブロック");
} catch (err) {
    console.log("catchブロック");
} finally {
    console.log("finallyブロック");
}
```
finallyブロックは、エラーが発生してもしなくても実行されます。つまり、tryブロックを実行してエラーが発生しなかった場合は、次にfinallyブロックが実行されます。
M5PaperのスクリーンショットをSDカードに書き出す

M5Paperでスクリーンショットを撮りたいときありますよね。スマホだったら撮れるんだからM5Paperでもうまく動いた画面を撮りたい。でもEPD Canvasには、pngやjpgを表示する関数はあるけど書き出す関数はなさそうです。
https://docs.m5stack.com/en/api/m5paper/epd_canvas
frameBuffer()でバッファにはアクセスできるので、それを何かビットマップな形式にして保存すればよさそう。一番フォーマットが簡単そうなPGMファイルにしてみます。

# コード

```C++
// Export EPD Canvas content as PGM file
void savePGM(M5EPD_Canvas canvas) {
  uint32_t bufferSize = canvas.getBufferSize();
  uint8_t *buffer = (uint8_t *)(canvas.frameBuffer(1));
  int width = canvas.width();
  int height = canvas.height();
  Serial.println(String(width) + " x " + String(height) + " = " + String(width * height) + ", bufferSize = " + String(bufferSize));

  // Open file
  int fileIndex = 1;
  while (SD.exists("/ss" + String(fileIndex) + ".pgm"))
  {
    fileIndex++;
  }
  String fileName = "/ss" + String(fileIndex) + ".pgm";
  Serial.println("File name = " + fileName);
  File pgmFile = SD.open(fileName, FILE_WRITE);
  if (!pgmFile) return;

  // Write header
  pgmFile.print("P5 ");
  pgmFile.print(String(width) + " ");
  pgmFile.print(String(height) + " ");
  pgmFile.print("255 ");

  // Write binary
  for(uint32_t i=0; i< (bufferSize); i++) {
    uint8_t byte = buffer[i];
    uint8_t high4bit = 17 * (15 - (byte >> 4));
    uint8_t low4bit = 17 * (15 - ((B00001111 & byte)));
    pgmFile.write(high4bit);
    pgmFile.write(low4bit);
  }
  pgmFile.close();
  Serial.println("File wrote: " + fileName);
}
```
# 実際の画像
![IMG_8300.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37794/bf4bef69-a5a6-673b-8002-8047d9fbeb2e.jpeg)
↑これが
↓こう
![ss6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37794/cd6ed648-ca75-f146-6765-4de3a6bfcaa9.png)

# やってること
同名ファイルが既にあったら番号を進めるようにしたりしてますが、固定ファイル名でもいいはずです。
PGMファイルのフォーマットはこちらを参照しました。
https://netpbm.sourceforge.net/doc/pgm.html

まず先頭に「P5」と書きます。これはグレイスケールのバイナリ形式であることを意味します。
空白文字（LFでもタブでもスペースでもいいらしい）を置いて、横ピクセル数、
また空白文字を置いて縦ピクセル数、また空白文字を置いてグレイスケールの最大値、
最後に空白文字を置いて以降はバイナリを書き込みます。

M5Paperは16階調の電子ペーパーなので4ビットでデータを持っているらしく、バッファから1バイト取得したらそれを4ビットと4ビットに分けます。また、バッファ上の階調は0が白、15が黒と、普通の画像ファイルとは逆になっているので反転します。最後に8ビットグレースケールに変換して、4ビットを1バイトにしてから書き込みます。

PGMファイルは圧縮が無いので、M5Paperの画面サイズだとそれなりにサイズが増えます。PNGだと28KBくらいのところPGMだと518KB。書き出しにも数秒かかります。

# 課題
- あくまでCanvasの書き出しなので、正確には画面全体のスクリーンショットではありません。全画面更新用のCanvasを使用している場合のみ有効です。
- 518KBはM5Paperには大きくてかわいそう。PNGにしたい
GAS チェックボックスでダイアログ開閉

下のようなものを作る。
+ A列にチェックボックスを並べる
+ チェックボックスをクリックしたら、複数選択できるダイアログボックスを表示
+ 選択された数値をチェックした行のC列に入力する。
![_GAS.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/d357f4bb-6ba0-eb3a-9f2b-332a319d13c5.gif)

---

# コード

code.gs
```js
function showDialog(e){
  // ⎾A列以外またはCheckBoxがオフ⏌ならば何もしない。
  if (e.range.columnStart !== 1 || e.value === 'FALSE') return;
  // HTMLを組み立て.
  let html = HtmlService.createTemplateFromFile('index');
  html.row = e.range.rowStart;
  html.col = e.range.columnStart;
  html = html.evaluate().setWidth(120).setHeight(140);
  SpreadsheetApp.getUi().showModalDialog(html, '入カ値を選択');
}

function inputValues(items, row){
  SpreadsheetApp.getActive().getActiveSheet().getRange('C' + row).setValue(items);
}
```
index.html
```HTML
<!DOCTYPE html>
<html>

<head>
  <base target="_top">
</head>
<style>
  .box select {
    width: 100px;
  }
</style>

<body>
  <div class="box">
    <select id="items" multiple>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
  </div>
  <div>
    <button id="inputdata">入カ</button>
  </div>
  <script>
    document.getElementById("inputdata").addEventListener("click", submit);
    function submit() {
      const items = document.getElementById('items');
      const selectedItems = Array.from(items, (item) => [item.selected, item.value] ).filter(e => e[0]).map(e => e[1]);
      console.log(selectedItems)
      google.script.run.inputValues(selectedItems.join(','),<?= row ?>);
      google.script.host.close();
    }
  </script>
</body>

</html>
```

トリガーで、「編集時」に「showDialog」が起動するように設定しておくこと。
laravel_データベースをつなぎ合わせる

# 必要なデータベースは既に作ったものとする
* 今回つなぎ合わせたいデータベースの図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2859224/145e1ad6-7e48-e01b-7113-61ce1fa7f8d6.png)

# 1.今回はContlloerを作成していなかったのでそこからやる！
```diff_ruby
./vendor/bin/sail php artisan make:controller OfficeController
+ ＊最後の名前の最初は大文字にすること！
```
## --resourceオプションについて
* Contlloerはindexやstoreやview（メソッド）など役割があるものをいれることで、使いこなすことができる！自分で追加しないといけないのだが--resourceオプションを使うことで、最初から入った状態で作業を行うことができる。
* 詳しいcontrollerのメソッドについて
https://www.wakuwakubank.com/posts/454-laravel-resourcefull-controller/

# 2.Controllerを作ったあとにやるべきこと
* １．まず、use を追加すること！
controllerを使いたい、データベースのuseと入っていないかもしれない　```use Validator;``` を
入れること！確認する！

# 3.次は、データベースのUserとOfficeをControllerを使ってつなぎます！

## *Authは今のユーザーの情報を取得する際に使う
```diff_ruby
public function index()
    {
+今ログインしているUserのoffice_idを$office_idに代入する！
        $office_id=Auth::user()->office_id;
+一回ずつdddを使って確認しながら追加していきましょう！
        //ddd($office_id);
+locationにOfficeテーブルにある$office_idのlocationを探してくる！
        $location=Office::find($office_id)->location;
        //ddd($location);
+office.index画面にこの情報を送る！（返す）
        return view('office.index');
    }
```
【環境構築】Docker + Apache + PHP + MySQL + phpMyAdmin + GDライブラリ

Dockerでの環境構築のメモ。


# ディレクトリ構造
```
sample
  ├── htdocs
  |    └── index.php
  ├── php
  |    ├──php.ini
  |    └── Dockerfile
  └── docker-compose.yml
```

# docker-compose.yml

```yml:docker-compose.yml
version: '3.7'

services:
  mysql:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 'password'

  phpmyadmin:
    depends_on:
      - mysql
    image: phpmyadmin/phpmyadmin
    environment:
      PMA_HOST: mysql
    restart: always
    ports:
      - "8080:80"

  php-apache:
    build: ./php
    volumes:
      - ./htdocs:/var/www/html
    restart: always
    ports:
      - "80:80"
    depends_on:
      - mysql

volumes:
    db_data: {}
```

# Dockerfile
--with-jpeg-dir でJPEGも有効になる。

https://www.php.net/manual/ja/book.image.php

```dockerfile:Dockerfile
FROM php:8.1.1-apache
COPY ./php.ini /usr/local/etc/php/
RUN apt-get update \
  && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
  && docker-php-ext-configure gd --with-jpeg-dir=/usr \
  && docker-php-ext-install pdo_mysql mysqli mbstring gd iconv
```

# php.ini

```ini:php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"
```

# html.php

```php:html.php
<?php
phpinfo();
```

# 起動

イメージのビルド
```terminal:
$ docker-compose build
```
イメージの確認
```terminal:
$ docker images
```
コンテナの起動
```terminal:
$ docker-compose up -d
```

# 動作確認

phpinfoにアクセスしてGDが有効になっているか確認

http://localhost/index.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2784589/ee6bd735-45ad-a825-5b52-78f8063dce27.png)

phpMyAdminにアクセス

http://localhost:8080

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2784589/b7feadef-6b7b-0209-210b-c4d2b3edd3ba.png)