OmniAICreator/Qiita-1.07M huggingface dataset:
SQLインジェクションについて調べる

# はじめに
[前回](https://qiita.com/player_Moke/items/e6049a135f73a6d2222e)セキュリティテストのための調査で、クロスサイト・スクリプティングについて調べましたが、今回はSQLインジェクションについて調べる上で知識が足りないと感じたので、調査していきます。

# SQLインジェクションとは
### どういう脅威？
SQL文の組み立て方法に問題があるとき、攻撃によってデータベースの不正利用を招く可能性がある脅威のことを**SQLインジェクションの脆弱性**と呼びます。

そして、これを悪用した攻撃を**SQLインジェクション攻撃**と呼びます。

### SQLインジェクション攻撃
データベースに不正なSQL文を注入することで、機密情報の取得や改ざん、削除などの被害をもたらします。
これらはユーザ入力に対して検証が不十分だったり、エスケープ処理を適切にできていなかったりすることが原因で発生します。

#### 発生しうる脅威の具体例
- **データベースに蓄積された非公開情報の閲覧**
    - 個人情報の漏洩
- **データベースに蓄積された情報の改ざん、消去**
    - Webページの改ざん
    - パスワード変更
    - システム停止
- **認証回避による不正ログイン**
    - ログインした利用者に許可されているすべての操作を不正に行われる
- **ストアドプロシージャ[^1]等を利用したOSコマンドの実行**
    - システムの乗っ取り、他の攻撃の踏み台としての悪用

[^1]:ストアドプロシージャ・・・データベースに対する連続した複数の処理を1つのプログラムにまとめ、データとともに保存できるようにしたもの

# 注意が必要なサイト
- データベースを利用するウェブアプリケーションを設置しているウェブサイト
    - 個人情報のような重要情報を扱っている場合は特に要注意

# 対策
IPAのサイトでは以下のような対策が紹介されています。

**根本的対策**
**①SQL文の組み立ては全てプレースホルダで実装する**
- SQL文の雛形の中に変数の場所を表す記号（プレースホルダ）を置いて、その後そこに実際の値を機械的な処理で割り当てる方法
- SQL文の組み立て方法には文字列連結処理という方法もあるが、機械的な処理で組み立てる分、SQLインジェクションの脆弱性を解消することができる

**②SQL文の組み立てを文字列連結により行う場合は、エスケープ処理等を行うデータベースエンジンのAPIを用いて、SQL文のリテラルを正しく構成する**
- 処理を文字列連結処理で行う場合は、対象の値に対してエスケープ処理を行う必要がある
- データベースエンジンによってはそのための専用のAPIを提供している場合もあるため、必要に応じて活用する
- この処理はSQL文を構成するすべてのリテラルに対して行うべき

**③ウェブアプリケーションに渡されるパラメータにSQL文を直接指定しない**
- hiddenパラメータ等にSQL文をそのまま指定する実装は、データベースの不正利用につながるので避ける

**保険的対策**
**①エラーメッセージをそのままブラウザに表示しない**
- アプリケーション上にエラーメッセージを出す際、データベースに関連するメッセージをそのまま出してしまうと、SQLインジェクション攻撃につながる情報が含まれている可能性がある
    - 特に、データベースの種類、エラーの原因、エラーを起こしたSQLに関する情報など
- そのため、それらの情報は利用者のブラウザに表示しない工夫が必要（別でメッセージを用意するなど）

**②データベースアカウントに適切な権限を与える**
- データベースアカウントに必要以上の権限が付与されていると、攻撃された際の被害が大きくなる恐れがあるため、必要最小限の権限を与えるようにする

### SQLのプレースホルダーとは
パラメータ部分を「?」などの記号で示しておき、後で実際の値を機械的に割り当てることができます。
プレースホルダに実際の値を割り当てることを**バインドする**といい、いつそれを行うかによって種類が変わってきます。
このバインドするために使っている記号をプレースホルダと呼びます。

以下はIPAの別冊：「安全なSQLの呼び出し方」で紹介されている分類です。

**静的プレースホルダ**
- プレースホルダのままのSQL文をデータベースエンジンにあらかじめ送信して、実行前にSQL文の構文解析などの準備をしておく方法
- SQL文の構文はバインド前に確定し、後からSQL構文が変化しないため、最も安全である

**動的プレースホルダ**
- プレースホルダを利用はするけど、データベースエンジンではなく、アプリケーション側でバインド処理を実行する方法
- この方法は開発者によるエスケープ漏れを防止できますが、ライブラリによってはSQLインジェクションに対して脆弱性がある

### Laravelでの対策
Laravalではプレースホルダを「:」を使って表すこともできます。
これにより、プレースホルダを多用した際にも指定を間違いにくくすることができます。

また、selectメソッドの第2引数は自動でサニタイズされ、SQLインジェクションを防止することができます。

Laravelで行えるSQLインジェクション対策については、まだ詳しくないので、少しずつ調べて行ってみようと思いました。



# 参照
https://www.ipa.go.jp/security/vuln/websecurity/sql.html

別冊：「安全なSQLの呼び出し方」

https://www.ipa.go.jp/security/vuln/websecurity/ug65p900000196e2-att/000017320.pdf

https://cybersecurity-jp.com/column/18402

https://e-words.jp/w/%E3%82%B9%E3%83%88%E3%82%A2%E3%83%89%E3%83%97%E3%83%AD%E3%82%B7%E3%83%BC%E3%82%B8%E3%83%A3.html

https://code-php.com/php-db/ps-name/

https://readouble.com/laravel/9.x/ja/database.html
TypeExpressTutorial - 5

:::note info
やりたいこと
- `/api/auth/signup` `POST` リクエストをして、新規でユーザーが作成する
- `/api/auth/login` `POST` リクエストして、ログインに成功した場合はユーザー情報とJWTのトークンを返却する
:::

## Routesの設定
```src/routes/auth.ts
import { Router } from 'express'

import { login, signup } from '../controllers/auth'

const authRouter: Router = Router()

authRouter.post('/signup', signup) // POSTリクエストにするため、 .post に変更
authRouter.post('/login', login) // POSTリクエストにするため、 .post に変更

export default authRouter
```

## Controller の処理
```src/controllers/auth.ts
import { Request, Response } from 'express'
import { compareSync, hashSync } from 'bcrypt'
import * as jwt from 'jsonwebtoken'

import { prisma } from '..'
import { JWT_SECRET } from '../secrets'

export const signup = async (req: Request, res: Response) => {
  const { email, password, name } = req.body

  const existingUser = await prisma.user.findFirst({
    where: {
      email,
    },
  })

  if (existingUser) {
    return res.status(400).json({ message: '既に存在するユーザーです' })
  }

  const user = await prisma.user.create({
    data: {
      email,
      name,
      password: hashSync(password, 10),
    },
  })

  res.json({ user })
}

export const login = async (req: Request, res: Response) => {
  const { email, password } = req.body

  const user = await prisma.user.findFirst({
    where: {
      email,
    },
  })

  if (!user) {
    return res.status(400).json({ message: 'ユーザーが見つかりません' })
  }

  if (!user.password) {
    return res.status(400).json({ message: 'パスワードが設定されていません' })
  }

  const validPassword = compareSync(password, user.password)

  if (!validPassword) {
    return res.status(400).json({ message: 'パスワードが間違っています' })
  }

  const token = jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: '1h' })

  res.json({ user, token })
}
```

### Tips
#### リクエストに含まれる値を取得したい
`req.body` を使って取得できる
上記では、分割代入を活用して `const { email, password, name } = req.body` のように記述している

#### パスワードをハッシュ化したい
`bcrypt` のライブラリの `hashSync`メソッドを使ってパスワードのハッシュ化ができる

#### ハッシュ化されたパスワードと受け取ったパスワードを比較して間違っていないか調べたい
`bcrypt` のライブラリの `compareSync`メソッドを使ってハッシュ化されたパスワードと入力されたパスワードを比較して真偽値を出せる

#### JWTのトークンを作成したい
`jsonwebtoken` のライブラリの `jwt` から `sign` メソッドを使って、JWTのトークンを生成できる
1Password で GitHub の SSH 鍵を管理できるって知ってた？

パスワードマネージャーとして有名な [1Password](https://1password.com/jp) は、開発者向けに **1Password CLI** を提供しています。

https://developer.1password.com/docs/cli/

その中で SSH 鍵を管理する機能が提供されています。

https://developer.1password.com/docs/ssh

このドキュメントに沿って、Git の SSH 鍵を 1Password で管理できるようにします。

# ステップ1: SSH 鍵を生成

`個人`または`プライベート`(Businessプランなどでは`従業員`)の保管庫で`新規アイテム`から`SSHキー`を選択します。

:::note warn
保管庫は上記のいずれかでないといけません。
自分で作成した保管庫などでは利用できません。
:::


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/787ee21d-3481-5c5c-0162-acf03263f8dc.png)

名前はお好きな名前に設定します。

`秘密鍵を追加`から`新しい鍵を生成`を押し、鍵の種類はどちらかを選びます。
そして`生成`をクリック。

鍵が生成されたら`保存`をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/2288ff97-a70f-e645-23a9-e6ab58405ee7.png)

# ステップ2: 公開鍵をアップロード

下記の GitHub の SSH 追加ページにアクセスし、先程作成した公開鍵をアップロードします。
https://github.com/settings/ssh/new

- Title
    - 任意の名前を追加
- Key type
    - Authentication Key
- Key
    - ここに公開鍵を追加します
    - ブラウザの1Password拡張機能を追加していれば、以下写真のように候補が出てきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/60657f6a-ef80-1680-76c3-ef757166162b.png)

最後に `Add SSH key` を押します。

# ステップ3: 1Password SSHエージェントを有効化する

1Password のデスクトップアプリには、SSH クライアントの認証を裏で制御する SSHエージェントの機能があります。

1. 1Password アプリの設定画面から`開発者`を選びます
1. `SSHエージェントを設定`を選びます 
1. その他のオプションを選択します。ここは任意に設定します
:::note warn
一度SSHエージェントを設定すると、`SSHエージェントを設定`の項目が`SSHエージェントを使用`に変わるため、設定前の日本語の文言が何であるか正確には私はわかりませんでした…
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/b45ecc93-88a0-94a8-5a6f-942e4c7b481a.png)

1Password アプリを閉じてもSSHエージェントを動作させ続けるために以下を実施します。

1. 1Password の設定画面から`一般`を選択
1. `メニューバーに1Passwordを固定する`と`ログイン時に起動する`のチェックボックスをオンにする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/3f69bcf7-5aca-b9bf-d58d-c914d3d55b8e.png)

# ステップ4: SSH クライアントを設定する

最後に SSH クライアントが 1Password の SSHエージェントを使うように設定をします。

`~/.ssh/config` ファイルに下記を追記します。
```
Host *
  IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
```

# ステップ5: SSH 接続を確認する

下記コマンドを実行して GitHub との SSH 接続を確認します。
```
ssh -T git@github.com
> Hi ****! You've successfully authenticated, but GitHub does not provide shell access.
```

これで `git clone` や `git pull` などを利用できます！

# 参考
https://developer.1password.com/docs/ssh/get-started/
Anthropic Claude3 API の Rate limits に達したとき

# 自社サービスサイトで発生した Rate limits

最新LLMを試したり、複数のLLMをおまとめして生成できる Playground的 [サービスサイト](https://chatstream.net/?ws_name=chat_app&mult=1&ontp=1&isync=1&model_id=llama_3_elyza_jp_8b&model_id=openai_gpt_3_5_175b&model_id=anthropic_claude3_0_haiku&model_id=google_gemini1_5_flash) （登録不要・無料です）の運用に携わっていますが、
さきほど Anthropic の Claude3-Haiku への同時リクエストが 7req/s を超えたあたりで、

```
Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}
```

が発生

わお、 Rate Limit だ😲

あわてて、APIの契約をみたら、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3752825/3691e2b4-f12a-4388-39c9-eabe01718e2e.png)

そっか。
いちばん下のレベルの Tier だったか。

Anthropic Claude3 の Tier 1 の Rate limit は１分間に50 なので、無意識にそれを秒間50リクエストと勘違いしておりましたが、
秒間スパイクで 7req/sec くらいのリミットなんですねー。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3752825/ae7b1460-9c12-a1a7-ff7a-62555bb21ab8.png)


リミットあげるにはプランをアップグレードが必要だけど、クリックだけでできるわけではなく Anthropic さんにフォームで依頼が必要なので、

さて、どうするか相談だ🤔

ちなみに、 Build Tier 2 では、Rate limit はイッキに20倍になるようなので、当面安心できそう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3752825/dd9df617-66ba-a7a5-cd6b-5a54c08886e7.png)

https://docs.anthropic.com/ja/api/rate-limits
プロンプトエンジニアリングのコツ

## はじめに
プロンプトエンジニアリング：
生成人工知能 (生成 AI) ソリューションをガイドして、目的の出力を生成させるプロセス。

ChatGPTに聞いても期待する答えが出ない、、、
間違った回答がでてきてしまう、、
みなさんこんな経験ないでしょうか？

本記事ではAWS Summit japanで紹介されていたプロンプトエンジニアリングのコツを紹介させていただきます。(Claudeによってしまう部分もありますがご了承ください）

## プロンプトエンジニアリングのコツ
### １．明確かつ直接的に
明確なプロンプトの黄金律：自分のプロンプトを友人に見せて、求めている結果を相手が出せるか聞いてみる

### ２．XMLタグを使ったセクションの描画
区切り目がないと理解しにくい
XMLタグを使うとプロンプトの構造を理解しやすくなる（セッションタイトルが人間の理解に役立つのと同様）

### ３．例示
＜example＞タグで囲って例を提示
例と同じ形式で質問　少なくとも3－5個の例を含むことを目指す

### ４．出力形式の指定
出力のフォーマットを指定する

### ５．考える時間を与える
思考過程と回答の出力を指定

### ６．役割のアサイン（ロールプロンプティング）
自分がどんな役割を担うべきかの背景情報を加える
役割を割り当てると特定の状況での精度向上，役割に合わせた口調と態度の変更が見込める

### ７，ハルシネーションへの対処
「知らない」と言う許可を与える
自信がある時だけ答えるように言う
適切な引用を見つけてその引用を使って答えるように頼む

### ８．プロンプトチェーン
ステップの多いタスクでは、タスクを分割する

## おわりに
生成ＡＩはエンジニアにとって非常な便利なツールです。プロンプトエンジニアリングを向上させることは活用していくうえで必須のスキルになると思います。
HTMLから、不意に何度呼ばれても安心なJavaScriptを書いてみる

## やりたいこと

外部からGETなどで読み込んだHTMLなどに、すでに読み込んだjavascriptが含まれていることがある。
そのときに例外が発生してしまい、処理が中断されるような状況を回避したい。

## 回避できる問題

- 変数や関数の再定義を回避する
- 公開したくない処理を隠蔽することで、意図せず他の処理に影響を与えることを防ぐ
- 値をグローバルに公開したいとき、スクリプトが呼び出されるたびに初期化されることを防ぐ

## 実装例

```sample.js
// 無名関数を即時実行することで、グローバルスコープに何も残さないようにしている (後述)
(() => {

  /**
   * 初回だけ初期化して、意図せず値が失われることを防ぐ
   * 例としてこの型を使っているが、実際には何でも良い
   *
   * @type { Set<any> }
   */
  const sharedData = (() => {

    // すでにデータが存在する場合はそれを返す
    if (window.__sharedData) {
      return window.__sharedData;
    }

    // 共有データを保持するためのSetを作成
    const sharedData = new Set();

    // グローバルスコープに共有データを登録
    // windowの中に存在しない名前をつけて、衝突を避けること。
    window.__sharedData = sharedData;

    return sharedData;

  })();


  // 外部ファイルに公開する関数群

  function addValue(...value) {
    value.forEach(v => sharedData.add(v));
  }

  function getSharedData() {
    return sharedData;
  }

  // 関数をグローバルスコープに登録
  window.addValue = addValue;
  window.getSharedData = getSharedData;


  /**
   * 公開しない関数
   */
  function hiddenFunction() {
    console.log('公開しない関数が実行されました');
  }

  hiddenFunction();

})();

```

## 実行結果

```sample.html
<script src="./sample.js"></script>
<script>
addValue("hoge", 1);
console.log(getSharedData());
// => Set(2) {'hoge', 1}
</script>

<!-- 無名関数で実行しているため、定義済みなどのエラーは発生しない -->
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script src="./sample.js"></script>
<script>
addValue("fuga", 123);
console.log(getSharedData());
// => Set(4) {'hoge', 1, 'fuga', 123}
</script>


<script>
// console.log(sharedData);
// => ReferenceError: sharedData is not defined

// hiddenFunction();
// => ReferenceError: hiddenFunction is not defined
</script>
```

コンソールには以下のようなログが出力される。

```console
VM555 sample.js:48 公開しない関数が実行されました
index.html:4 Set(2) {'hoge', 1}
VM557 sample.js:48 公開しない関数が実行されました
VM558 sample.js:48 公開しない関数が実行されました
VM559 sample.js:48 公開しない関数が実行されました
VM560 sample.js:48 公開しない関数が実行されました
VM561 sample.js:48 公開しない関数が実行されました
sample.js:48 公開しない関数が実行されました
index.html:17 Set(4) {'hoge', 1, 'fuga', 123}
```

## 無名関数を即時実行する理由

初期化をする関数に名前を付けた場合、その時点でグローバルスコープに関数が登録される。
例として以下のような`init()`を定義した場合、既存の`window.init`を上書きすることになる。

```js
function init() {
  // 初期化処理
}

window.init();  // 動作する
init();         // もちろん動作する
```

このような問題を回避するために、即時実行関数を使っている。

```js
(() => {
  // 初期化処理
})();

// windowオブジェクトに何も登録されない
```
別業界(未経験)からエンジニアにキャリアチェンジする際の心得

私は別業界からキャリアチェンジを経て、現在株式会社add moreでWebエンジニアとして働いており、間もなく5年目を迎えようとしています。Web業界に飛び込んだことは、とても良い決断だったと感じています。この記事が、ITエンジニアへのキャリアチェンジを考えている方の参考になれば幸いです。

## 自己紹介
[add more](https://www.add-more.co.jp/ ) (Web制作会社)のエンジニアとして働いています。
私は別業界からの転職で、エンジニアになる前に2社で働いていました。
1社目では、大学で工学を学んだ経験を活かし、機械部品の設計業務に従事していました。
2社目は医療機器メーカーで営業職に従事していました。
どちらの職場でも上司や同僚に恵まれ、充実した日々を過ごしていましたが、将来に対する漠然とした不安が拭えず、転職を決意しました。

## Web業界に飛び込んだ理由
ひとことで言うと「Web業界に可能性を感じたから」です。
Webは効率的で柔軟な情報発信ツールであり、デジタルの世界でありながらもリアルな影響力を持つことができます。
この可能性を活用して、より多くの人に価値を届けることができる点に大きな魅力を感じています。
ですがWebというと実体が無く、なにやら難しい印象を受ける人もいるかもしれません。
実際私もそうでした。
私が社会人になったばかりを思い返していると、HTML/CSSといわれてもよくわからないという状態でした。
ですが実際のところWebはただの道具で、何か目的を達成するための一つの手段でしかありません。

突然ですが、あなたがティッシュ配りのアルバイトをしている姿を想像してください。
ティッシュ配りをするということは、何かを告知したいという目的があり、その手段として行動しているということです。
どんなに人の通りが多いところでティッシュを配り続けたとしても、情報を届けられるのはその日用意しているティッシュの数だけということになります。
しかし、Webの場合はどうでしょう。一度作成した成果物は半永久に残り続け、あなたが街に立たずとも休まず情報を届けてくれます。
そしてリーチできる人数は青天井で、どこからでも情報にアクセスできます。
今こうしている間にも誰かの目に留まっているでしょう。
さらに古くなった情報はすぐに更新する事ができ、常にリアルタイムで最新の情報を届けることができます。
それ以外にも、Webの良い点を挙げればたくさん出てくるでしょう。

このように、Webは非常に効率的で柔軟な情報発信ツールです。
Web制作は、デジタルの世界でありながら、リアルな影響力を持つことができる。その可能性を活用し、より多くの人に価値を届けることができるという点に、私は大きな魅力を感じています。

## 私が思うWeb業界の魅力
私はこれまで2社経験しましたが、やはりWeb業界は楽しいと感じています。
Web業界は常に技術革新が進んでおり、新しいチャレンジが次々と現れる点が好きです。勉強熱心で向上心のあるメンバーが多いことも魅力的だと考えています。
このような環境に身を置くことで、常に新しい知識や技術を学び続けることができ、自分自身の成長を実感できます。加えて、最新の技術トレンドに触れながら、自分のスキルを磨くことができるため、日々の業務が非常に充実しています。
Web業界は、常に新しい発見や学びがあるため、毎日が新鮮です。これからもこの業界で、自分自身を高めながら、価値あるプロジェクトに携わり続けていきたいと思います。

## これからWeb業界に進もうと考えるあなたへお願い
まず、なぜWeb業界に進みたいと思ったのかを明確にしてください。
よく「在宅でPCやスマホでスキマ時間に働きましょう！」のような手軽さを謳ったネット広告を見かけますが、それほど美化された世界ではありません。
ひとまず「私にもエンジニアになれるでしょうか？」という質問に対してお答えさせていただくと確実になれます。
しかし、それよりも「Web業界に飛び込んで何がしたいのか」という問いに対する答えを用意しておくことのほうが重要です。
私たちの仕事は顧客の問題解決です。AIの発達が著しい昨今、単純な作業は減少していくことが予想されます。
少し前に話題になったハーバードの哲学者マイケル・サンデル教授のトロッコ問題の話をさせてください。

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1078393/d922ac96-2097-2ae7-873b-058e326d71ed.png)


この話をおさらいしておくと、制御不能になったトロッコの先には、分岐があり一方は線路工事をしている作業員が５人います。また一方には作業員一人が作業をしています。

あなただけが制御不能になったトロッコの危機に気づいており、あなただけがトロッコの進路を変更することができます。
レバーを引けば線路が切り替わり5人を救えますが、今度は切り替えた先にいる1人の作業員が犠牲になってしまいます。
あなたは、1人と5人、どちらの命を選びますか？という答えのないこの問い。
私たちの普段の仕事は、このような決断の連続です。決断には痛みが伴いますが、顧客の課題を理解し、寄り添い、決断の手助けをすることに価値があると私は考えています。
そのために、自身を高め、挑戦し続ける姿勢を持ち、そして常に学びを得ようとする謙虚さが一層大切になると私は考えます。

Web業界は確かに厳しい面もありますが、その分やりがいも大きいです。自分が何を目指し、どのように成長していきたいのかをしっかりと見据えながら挑戦することで、大きな成果を得ることができるでしょう。もしあなたが今のキャリアを変えてWeb業界へ進もうと考えるなら、その思いを明確にし、ぜひ挑戦してみてください。

add moreでは、Webを通じてお客様の課題に向き合いたいという想いを持つ仲間を募集しています。
我々と共に成長し、顧客に寄り添いながら解決策を提供したいという思いをお持ちの方は、お気軽にお問い合わせください。
[GitHub] GitHub CLI をインストールする

# まとめ
> GitHub CLI は、コンピューターのコマンド ラインから GitHub を使用するためのオープン ソース ツールです。 コマンドラインから作業しているときは、GitHub CLI を使用して時間を節約し、コンテキストの切り替えを回避できます。

# 環境
MacBook Pro (2.3 GHz 8コアIntel Core i9)
macOS 14.0（23A344）
Homebrew 4.3.8
gh 2.52.0

# 目次

# インストールする
~~~sh
brew install gh
~~~

# GitHub CLI の認証を行う
~~~sh
gh auth login
~~~

# 質問に答える
~~~sh
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: F687-EF40
Press Enter to open github.com in your browser... 
~~~

# ログインする
1. ブラウザが開く
1. `one-time code`を使用してログイン


# 参考リンク
- [GitHub CLI クイックスタート](https://docs.github.com/ja/github-cli/github-cli/quickstart)
- [リポジトリをクローンする](https://docs.github.com/ja/repositories/creating-and-managing-repositories/cloning-a-repository)
- [MacでGitHub CLIをセットアップする](https://zenn.dev/krabben16/articles/setup-github-cli-for-mac)
C_TS422_2021 SAP Certified Application Associate SAP S/4HANA Production Planning and Manufacturing対策（39/100）

【39日目】
トピック4：SAPS/4HANAの資材所要量計画（MRP）（6/10）

≪問題39≫
MRPで逆日程計画に考慮され、従属所要量から構成品目のオーダー日付を決定する時間要素はどれですか。
（２つ選択）
□内製日数
□作業期間
□総補充リードタイム
□納入予定日数

≪解答≫
■内製日数
□作業期間
□総補充リードタイム
■納入予定日数

≪解説≫
◆調達または製造日付は、最終製品の品目利用可能日（受注などから取得）に関連します。最終製品の要求日付に基づき、BOM構造全体で多段階逆日程計画が実行され、可能であれば、逆日程計画を使用して、下位レベルの品目の従属所要量の所要日付が決定されます。
◆内製と外部調達に対する逆日程計画の時間要素は以下のとおりです。
（1）内製の場合
- 利用可能日 - 入庫処理日数 = 手配終了日
- 手配終了日 - 内製日数 = 手配開始日
- 手配開始日 - 開放期間 = 開放日
（2）外部調達の場合
- 手配終了日 - 納入予定日数 - 購買処理日数 = 手配開始日
Windows Server 2025 バグ報告

[以前の記事](https://qiita.com/hidekko/items/a701c55824634d2aac54)で最後に少しだけ記載しているWindows Server 2025でSysprepをかけて初期化しクローンして利用する場合に出現するバグの追加報告です。
やはりエクスプローラー機能が使えません。
3回やってみて3回とも再現しました。
といってもまだ3回なので全然少ないのですが、状況を記載します。

# Sysprep をかけてみる
Sysprepそのものは問題なく実施できます。

# Sysprep をかけたイメージを複製し、初期セットアップを行う
ここも問題なく進められます。
クローン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/f7786c97-496d-4f44-ce7b-f71aaa72dbe2.png)
初期セットアップ画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/2e81cf52-6459-f340-e9c8-d38d3e37c688.png)

# 起動後、タスクバーが表示されない
タスクバーが表示されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/d7094bfb-6554-a9b1-8616-b34cbbc5e54c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/0666d4a6-295d-3bbc-2d26-7d447435ac38.png)

# Explorer機能が使えない
```Windows キー``` + ```E```での起動するエクスプローラーが上がってこず、画面が一瞬真っ黒になり何事もなかったかのようにデスクトップが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/6c99c9e7-efa4-5cf0-7f0a-e5f46343fbdb.png)

```Windows キー``` + ```R```で起動させる```ファイル名 を指定して実行```は起動するので、```Windows キー```そのものはRDP経由でKVMのコンソールに届いていて処理できているのは間違いないです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/fa8bd76f-2d86-52af-d730-1d041b24dae2.png)

```ファイル名 を指定して実行```に```explorer.exe```と打ち込んでエクスプローラーを起動しようとしても
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/962ea0b1-402c-bb42-8f07-38e08d6524f9.png)
```Windows キー``` + ```E```と同じく一瞬真っ黒になり何も起きません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/1f5d2d43-0d74-db7d-9f92-ee8b668c46dd.png)

Windows Updateすると復活するかもと思い
```ファイル名を 指定して実行```に```ms-setting:about```と打ち込んでもエラーが返ってきて設定画面が起動せず、Windows Updateもできませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/63f2f8ad-6922-ca21-027c-4d596e909ffe.png)
```ファイル名を 指定して実行```に```ms-setting:windowsupdate```と打ち込んでも同様のエラーが返ってきます。

# 各種設定をコマンドベースでやってみたらちゃんと動くんじゃないの？
これは試してみたいんですが今のところまだ実施していません。
仮にこの方法でうまくいったとしても、そもそもSysprepからクローンしたWindows Server 2025はExplorerが起動しない状態なので、<font color="Red">**「Microsoftの設計通り正しく動作していたのか？」という根本的な疑義が解消されません。**</font>
知的好奇心を満たすための検証であれば良いのでしょうが、エンタープライズ向けに利用することを前提に考えるとこのような根本的な疑義が解消されない環境を提供すべきではなく、勿論検証しても意味がないので現時点ではここをゴリゴリ深堀しようとは思っていません。

本日はここまで。
[Mac] [Terminal] cdコマンドの使い方

# 目次
- [MacOSのディレクトリ構成](#MacOSのディレクトリ構成)
- [cdコマンド](#cdコマンド)

# MacOSのディレクトリ構成
~~~:directory_structure
drive/ ←ルートディレクトリ
├── Applications/
├── Syetem/
└── Usr/
│   ├── shared/
│   └── personnel(ex. Jochun)/　←ホームディレクトリ
│       ├── Desktop/
│       ├── Downloads/
│       ├── Documents/
│       ├── .config/
│       ├── .ssh/
│       ├── .zshrc/
│       └── .local/
│       │   ├── .bin
│       │   ├── 
~~~

# ホームディレクトリ
`~`はホームディレクトリの印
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/df37c61f-af94-87df-5ccf-a6e5aa6c8786.png)
[引用](https://original-game.com/how-to-use-the-cd-command-on-mac/)

# cdコマンド
### 移動
Desktopに移動したい場合
~~~
cd Desktop
~~~
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/f253d1e2-a1be-4542-d8db-248522ffdf13.png)

### 1階層上に戻る
~~~
cd ..
~~~

### 2階層上に戻る
~~~
cd ../..
~~~

### 3階層上に戻る
~~~
cd ../../..
~~~

### 同階層の移動
Documentsに移動したい場合
~~~
cd ../Documents
~~~

### ドライブ直下（ルートディレクトリ）に移動する
~~~
cd /
~~~

### ホームディレクトリに移動する
~~~
cd ~
~~~


# 参考リンク
- [【初心者向け】cdコマンドの使い方を分かりやすく解説！（Mac）]([引用](https://original-game.com/how-to-use-the-cd-command-on-mac/))
- [コマンドプロンプトでフォルダ移動する方法【cd/戻る/上の階層】](https://aprico-media.com/posts/4392)
- [https://epic-life.me/archives/1914](https://epic-life.me/archives/1914)
ITIL4 ファンデーションに一発合格しました

# はじめに
ある機会に恵まれて「ITIL4 ファンデーション」の資格を取得することになり、一発で合格できたので調べたことと勉強方法をまとめます。ちなみに、100点中85点でした（65点合格）。

# 概要
ITIL4ファンデーション（ITIL 4 Foundation）は、ITIL（Information Technology Infrastructure Library）の最新バージョンであるITIL 4の入門資格です。

ITILは、ITサービスマネジメントのベストプラクティスを提供するフレームワークで、世界中の組織がITサービスの提供と管理を改善するために使用しています。
なお、ITIL3からITIL4になったことで用語の意味などがかなり変わったようです。勉強される際に注意した方が良いかもしれないです。
また、受験料が約61,000円なので高額ですね。

# 主な勉強内容
以下のようなことを勉強します。
1. ITIL 4の概要：ITILの基本原則やその進化について
1. サービスマネジメントの概念：サービス、顧客、価値などの基本概念
1. ITIL 4のサービスバリューシステム（SVS）：組織が価値を創出するための全体的なモデル
1. ITIL 4のサービスバリューチェーン：サービスの計画、設計、提供、サポートを通じて価値を提供するための活動
1. ITIL 4の4つの次元：サービスマネジメントの4つの視点（組織と人、情報と技術、パートナーとサプライヤー、価値ストリームとプロセス）
1. ITIL 4のプラクティス：ITサービスマネジメントのための34のプラクティス（プロセスや機能）

# 受験方法
テストセンターかオンライン受験を選択できます。私はテストセンターで受験しました。
以下のサイトからITIL4ファンデーションの試験を検索してログイン後、試験予約ができます。

https://www.prometric-jp.com/examinee/test_list/archives/9



# 勉強方法
以下のサイトを使って問題演習を繰り返し行いました。有料の問題集や教科書等は購入していません。体系的に知識をつけたければ参考書を買った方が良いと思います。

↓会員登録が必要です（無料）。

https://it-concepts-japan.com/itil4-foundation

https://www.jpnshiken.com/exam/ITIL-4-Foundation-JPN-itil-4-foundation-exam-itil-4-foundation%E6%97%A5%E6%9C%AC%E8%AA%9E%E7%89%88-11007.html


所々日本語として全く意味が分からない問題や解答がありましたが、まるっと覚えちゃいました。


# 感想
- 個人的には運用・保守をしていた時の経験が活きたのか、ITIL4ファンデーションは簡単な資格だったと感じており、勉強時間も他の資格に比べて少なく20～30時間程だと思います

- 試験当日は10分くらいで解き終わりました。対策していた問題の中でもかなり簡単な部類に入るような問題が7問くらい出たので、正直全く勉強してなくても40点くらいはとれそうな感触でした
- 試験終了後その場で合格か不合格かだけが画面に表示されました。その後スマホを見たらメールでスコアレポートが届いていました
（Javascript）スプレッド構文（...）を使ってオブジェクトを展開してみよう

:::note warn
初投稿の記事になります。
:::

### 1つのオブジェクトを展開し、変数に代入してみる

```a.js
const userObject = { userName: 'John', age: 20, city: 'New York' }
const userObjectCopy = { ...userObject }
console.log(userObjectCopy)
```

```
{ userName: 'John', age: 20, city: 'New York' }
```

### 2つのオブジェクトを展開し、変数に代入してみる

```b.js
const personObject = { userName: 'John', age: 20 }
const dataObject = { city: 'New York', job: 'Engineer' }
const mergedUserObject = { ...personObject, ...dataObject }
console.log(mergedUserObject)
```

```
{ userName:"John", age:20, city:"New York", job:"Engineer"}
```

### （発展）2つのオブジェクトに同じプロパティがある場合

```c.js
const personObject = { userName: 'John', city: 'New York' }
const dataObject = { city: 'Tokyo', job: 'Engineer' }
const mergedUserObject = { ...personObject, ...dataObject }
console.log(mergedUserObject)
```
両オブジェクトとも、cityプロパティを持っているが果たして結果は…

```
{ userName:"John", city:"Tokyo", job:"Engineer"}
```
dataObjectオブジェクトの値（"Tokyo"）が採用されていることが確認できた。
2つのオブジェクト間でプロパティが競合した場合、2つ目のオブジェクトの値が採用される。（後勝ちとなる。）
MountしたドライブのDirのOwnerが変更できない

# 問題
外付けSSDをUbuntu 22.05にさしてMountしたディレクトリ・ファイルのOwershipが変更出来ない。

ubuntu:/mnt/data# chown satoshi：satoshi satoshi_home/
chown: 'satoshi_home/' の所有者を変更中: 許可されていない操作です

sudoでやっても、rootでログインしても同じ。

# 環境
Ubuntu 22.05

# 結論
Mountする際に、マウントする際にuidとgidを指定する。

ubuntu:/mnt/data$ sudo mount -t exfat -o rw,uid=1002,gid=1002 /dev/sdc1 /mnt/data

hiro@ubuntu:/mnt/data$ ls -la
合計 644
drwxr-xr-x 6 satoshi satoshi 131072 Jul  5 19:07 .
drwxr-xr-x 5 root    root      4096 Jul  4 22:18 ..
drwxr-xr-x 2 satoshi satoshi 131072 Jul  5 18:51 satoshi_home
定期的なHVACメンテナンスのメリット

HVACシステムのメンテナンスは、快適で効率的、そして健康的な家庭環境を確保するために非常に重要です。定期的なメンテナンスは、予期せぬ故障を防ぎ、エネルギー効率を高め、システムの寿命を延ばすのに役立ちます。この記事では、定期的なHVACメンテナンスの包括的な利点について説明し、それがすべての住宅所有者にとって不可欠な習慣である理由を強調します。

エネルギー効率の向上
定期的なHVACメンテナンスの主な利点の一つは、エネルギー効率の向上です。システムのメンテナンスが行き届いていると、より効率的に作動し、暖房や冷房に消費するエネルギーが少なくなります。

Tradeventのダクトヒーター・メルボルン・サービス（https://www.tradevent.com.au/ducted-heating-melbourne/）で、暖かく快適にお過ごしください。

フィルターの清掃と交換
フィルターが汚れていると、空気の流れが妨げられ、HVACシステムがより強く働き、より多くのエネルギーを消費する原因となります。フィルターを定期的に清掃または交換することで、最適な空気の流れを確保し、効率を高め、エネルギー料金を削減します。

ダクトの点検とシーリング
ダクトからの漏れは、調整された空気が目的地に到達する前に逃げてしまうため、大幅なエネルギー損失につながります。定期的なメンテナンスでは、ダクトの点検とシーリングを行って漏れを防ぎ、システムが最高の効率で作動するようにします。

システム寿命の延長
定期的なメンテナンスは、HVACシステムの寿命を大幅に延ばすことができます。小さな問題が大きな問題になる前に対処することで、費用のかかる修理や早期のシステム交換を避けることができます。

消耗の防止
定期的な点検とチューンナップは、システム部品の摩耗や損傷を防ぐのに役立ちます。この積極的なアプローチにより、部品が良好な作動状態に保たれ、故障のリスクを低減し、HVACシステム全体の寿命を延ばします。

可動部品の潤滑
可動部品の適切な潤滑は、摩擦や摩耗を最小限に抑えるために不可欠です。定期的なメンテナンスでは、モーターやベアリングなどの部品に注油することで、システムのスムーズな稼働を維持し、寿命を延ばすことができます。

室内空気の質の向上
室内の空気の質を良好に保つことは、ご家族の健康と快適さのために非常に重要です。定期的なHVACメンテナンスは、ご家庭内の空気を汚染物質のないきれいな状態に保つために重要な役割を果たします。

コイルと送風機の清掃
コイルや送風機にホコリやゴミがたまると、システムの効率が低下し、空気の質が損なわれます。これらの部品を定期的に清掃することで、最適な性能を維持し、家中を循環する空気をきれいに保つことができます。

ダクトの点検と清掃
時間の経過とともに、ほこりやカビなどの汚染物質がダクト内に蓄積することがあります。ダクトを定期的に点検・清掃することで、これらの汚染物質が家中を循環するのを防ぎ、室内の空気の質を向上させます。

故障のリスクを軽減
定期的なHVACメンテナンスは、予期せぬ故障のリスクを軽減し、最も必要な時にシステムが確実に作動することを保証します。

潜在的な問題の特定
定期メンテナンスの際、技術者はベルトの摩耗、接続部の緩み、冷媒漏れなどの潜在的な問題を特定することができます。これらの問題に早期に対処することで、大規模な修理やシステムの故障に発展するのを防ぎます。

適切な運転の確保
定期的なメンテナンスには、すべてのコンポーネントが正しく機能していることを確認するためのシステムのテストが含まれます。この徹底的なチェックにより、あらゆる不都合を発見し、故障につながる前に修正することができます。

コスト削減
定期的なHVACメンテナンスへの投資は、時間の経過とともに大幅なコスト削減につながります。効率を改善し、システムの寿命を延ばし、費用のかかる修理を防ぐことで、メンテナンスは長期的なコスト削減に役立ちます。

エネルギー料金の削減
効率的なHVACシステムは、より少ないエネルギーを消費し、光熱費の削減につながります。定期的なメンテナンスは、システムを効率的に稼働させ、毎月のエネルギーコストを削減します。

大規模な修理の回避
定期的なメンテナンスで小さな問題に対処することで、大規模な修理に伴う高額な費用を避けることができます。予防メンテナンスは、問題が発生する前に予防することで、費用を節約する費用対効果の高い戦略です。

トレードベントの空調ソリューションで、優れた快適性と効率性をご体験ください。https://www.tradevent.com.au/air-conditioning-melbourne/.

定期的なHVACメンテナンスは、エネルギー効率の向上、システム寿命の延長、室内空気の質の向上、故障リスクの低減、コスト削減など、数多くのメリットをもたらします。定期的なメンテナンスに投資することで、HVACシステムが確実かつ効率的に作動し、ご家族に快適で健康的な生活環境を提供することができます。定期的なメンテナンスを優先して投資を保護し、それがもたらす多くのメリットを享受しましょう。
GoogleのOauth2でリフレッシュトークンが返ってこない


GoogleのOAuth2の実装時にリフレッシュトークンが必要になり、`access_type=offline`を設定するだけだとAPIから帰ってこないケースがあったのでその時の解決方法を備忘録で残しておく。

結論から言うと、`prompt=consent`をさらにリンクに付け足す必要があった。

`prompt=force`を設定する方法を紹介する記事も見かけたが、仕様変更なのか自分が試したときはinvalidになってしまったので注意（2024/07/05時点）。

全体URLはこんな感じ。
```
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=openid email profile&access_type=offline&prompt=consent
```



※以下の記事が詳しい
https://www.eisbahn.jp/yoichiro/2012/05/google_oauth2_web_server_profile_refreshtoken.html#gsc.tab=0
Fit&Gap（フィットアンドギャップ）分析とは？注意点や要件定義との違いをわかりやすく解説–情報システム部門を刺激するメディア「情シスのじかん」より

「情シスのじかん」は、情報システム部門のプロフェッショナルを対象としたメディアです。実務に役立つノウハウや革新的なアイデアを提供し、学びとネットワーキングの場を提供することを目指しています。最新の技術動向やベストプラクティス、成功事例を通じて、情シス担当者のスキルアップをサポートします。

ここで、Qiitaの読者の皆様向けに、「情シスのじかん」で公開された記事をピックアップ＆サマリ紹介します！Qiitaは技術者向けの情報共有プラットフォームとして、より実践的で具体的な技術情報を提供する場です。情シスのじかんは、特に情報システム部門に特化した内容を中心にしていますが、両者の強みを活かして、プロフェッショナルの皆様に有益な情報をお届けします。

### Fit&Gap（フィットアンドギャップ）分析とは？注意点や要件定義との違いをわかりやすく解説
![FIT.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816554/9090ab29-7a67-4307-2012-a840573ba8d9.jpeg)

Fit&Gap（フィットアンドギャップ）分析とは、ERPや他のシステム導入時に自社の要件を明確にするための手法です。本記事では、分析の概要や目的、手順、注意点をわかりやすく解説しています。Fit&Gap分析を通じて、最適なシステム選定やカスタマイズの必要性を判断できます。システム導入を検討している方や要件定義に悩んでいる方におすすめです。

https://josys.wingarc.com/josys-keyword-fit-and-gap

### 情報システム部門を刺激するメディア「情シスのじかん」とは？
![情シスのじかんbyWingArc1st (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816554/89afaa7e-b941-a10a-c813-8485daca4502.png)

初めまして、『情シスのじかん』です。
近日遂に、日本中の情シス部門のあなたを刺激するメディアが発足します！🚨
さぁ、テクノロジーを飼い慣らし、仕事や生活の質を向上させ、一緒に未来を切り拓きましょう。皆様の生活リズムに合わせた情報発信で、常に寄り添い、共に未来を切り拓くためのメディアです。

ぜひ、情報システム部門の皆様の活動の取材をさせてください。

https://josys.wingarc.com/about
Flutter Rust Coreを使った（続）通知モーダルのミューテックス排他制御

## これは焼き直しです
以前、通知モーダルのミューテックス排他制御の記事を書きましたが、そのときは自前？でMutexを用意していました。

この度めでたく[『Rust Core 1.0』がリリース](https://www.publickey1.jp/blog/24/rustdartrust_core_10rustdart.html)されたことを受けて、**Rustには標準でMutexの仕組みが備わっている**こともあり、早速使ってみました。

:::note info
注意として、上記パッケージはRustの機能をDart言語で実装したものであって、DartからRustを呼び出すffiというわけではありません。
:::

https://qiita.com/kitaike/items/d1c753b838af8a59f9f0

## 解決したいこと
連続で表示する通知モーダルの排他制御です。

:::note info
詳しくは前回の記事を読んでください
:::



## Rust Coreで修正する

まず公式ドキュメントにしたがってパッケージを導入します。

https://mcmah309.github.io/rust_core/introduction/introduction.html

```shell
flutter pub add rust_core
```

あとは手順としては前回の記事と同じです。
rust_coreパッケージ（のsync）をimportした上で、排他制御ごとのサービスプロバイダーを定義します。

```dart:mutex_service.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
//rust_coreパッケージを使う
import 'package:rust_core/sync.dart';

//以下は前回記事と一緒

//排他制御をするグループ単位のProvider
final mutexServiceProvider =
    Provider.family((ref, MutexGroupKey key) => Mutex());

//グループのキー
enum MutexGroupKey { notifyModal }

```
パッケージが変われば使用方法も変わるということで、今回はRust Coreの`Mutex`クラスの`withLock`メソッド使います。

`withLock`メソッドには、その[コード](https://github.com/mcmah309/rust_core/blob/master/lib/src/sync/mutex.dart#L5-L32)を見れば分かる通り、
(a)タスク実行前のロックの取得
(b)`try`句でのタスクの実行
(c)`finally`句でのロックの解放
がパッケージ側で実装済みなので使うだけです。

```dart:notification.dart
  //rust_coreパッケージを使う
  import 'package:rust_core/sync.dart';

  //この辺は前回記事と一緒
  final _mutexGroupKey = MutexGroupKey.notifyModal;
  final _mutexService = ref.read(mutexServiceProvider(_mutexGroupKey));
  
  //通知が来たときに実行する関数
  Future<void> _handleMessage(RemoteMessage message) async {
    //ロックの取得&解放は実装済みなので、withLock呼ぶだけ
    await _mutexService.withLock(() async {
        //ミューテック対象としたいタスク（本例はモーダルボトムシート表示）
        await showBarModalBottomSheet(ModalArguments(message)); 
    });

```

## 結論
前回よりももっとシンプルに実装できました:tada:

## 最後に宣伝
CBcloudではモバイルエンジニア募集中です！

https://herp.careers/v1/cbcloud1025/DwSvmDjmzOlP

沖縄では隔月（場合によっては3ヶ月に一度）でFlutterコミュニティ活動をしています！
旅行ついでに立ち寄ってみてください:airplane::airplane:

https://flutter-okinawa.connpass.com/
ros2のbagをcsvに変換する

本記事では，ros2のbagファイルをcsvに変換する方法を説明します．

・ros2bag_to_csvをコマンドで実行する方法

(execute)
```
ros2 topic echo /nmea_1/nmea_sentence nmea_msgs/msg/Sentence --csv --qos-history keep_all --qos-reliability reliable > nmea_output.csv 
ros2 topic echo /localization/gnss/pose_with_covariance geometry_msgs/msg/PoseWithCovarianceStamped --csv --qos-history keep_all --qos-reliability reliable > gnss.csv
ros2 topic echo /localization/pose_estimator/pose_with_covariance geometry_msgs/msg/PoseWithCovarianceStamped --csv --qos-history keep_all --qos-reliability reliable > ndt.csv
ros2 topic echo /localization/pose_twist_fusion_filter/biased_pose_with_covariance geometry_msgs/msg/PoseWithCovarianceStamped --csv --qos-history keep_all --qos-reliability reliable > ekf.csv
```
(example)
```(example)
ros2 topic echo /YOUR_TOPIC your_msgs/msg/YourMsgType --csv --qos-history keep_all --qos-reliability reliable > output.csv
```
(execute)
```(execute)
ros2 bag play -r 100 '/home/megken/rosbag2_2024_06_11-19_34_20' 
```
(example)
```(example)
ros2 bag play -r 100 YOUR_BAG_DIR
```

参考URL
https://zenn.dev/xen_nippon/articles/0ae35719f6cc44

・NMEAセンテンスについて
参考URL
https://ales-corp.co.jp/technical-information-nmea/

・DEG形式からDMM形式変換
参考URL
https://www.tokyo2show.co.jp/knowledge/?p=15
C_TS422_2021 SAP Certified Application Associate SAP S/4HANA Production Planning and Manufacturing対策（38/100）

【38日目】
トピック4：SAPS/4HANAの資材所要量計画（MRP）（5/10）

≪問題38≫
MRPによる正味所要量計算中には、以下のどの要素が考慮されますか。
（２つ選択）
□安全在庫
□予測キー数値
□購買発注
□最大在庫レベル

≪解答≫
■安全在庫
□予測キー数値
■購買発注
□最大在庫レベル

≪解説≫
◆正味所要量計算では、確定入庫（購買発注、製造指図、確定計画手配、確定購買依頼など）を含む利用可能在庫と、所要量（受注、計画独立所要量、従属所要量、入出庫予定など）および安全在庫を比較します。
◆所要量を満たすための十分な在庫またはオーダーがない場合、不足が発生します。その場合、ロットサイズ計算によって決定された入庫のサイズに基づいて調達が提案されます。
Rustで100本ノックに挑戦できるリポジトリを作りました

## はじめに

元々は私が 100 本ノックを解くために書いていたコードですが、折角テストコードまで書いたので、誰もが挑戦できる形で公開することにしました。

https://github.com/ahyaemon/rust_100knocks

Rust の文法を一通り学び終わった方や、これから Rust で競技プログラミングに挑もうとしている方の前座等として挑戦してみてほしいです。

問題は [基礎プロI 100本ノック](https://www.cc.kyoto-su.ac.jp/~mmina/bp1/hundredKnocks.html) 様から拝借しています。
問題の一部はテストしやすい形に改変したり、グラフィックス関連の問題 (No.60 - No.79) はまるっとスキップしたりしています。

## 挑戦するには

挑戦のために`challenge` ブランチを作成しました。
リポジトリをローカルに `clone` して、`challenge` ブランチをチェックアウトすればチャレンジできます。(clone した時とかにリポジトリにスターをつけてくださると嬉しいです)

このチャレンジで目指すのは、`cargo test` 実行時に走る約 140 個のテスト全てを通すことです。
チェックアウト直後は解答部分のコードが全て `todo!()` に置き換わっているため、テストは全て落ちます。
各問題の `todo!()` 部分を修正して、全テストが通ればクリアです！

問題自体は、`src/knocks/` 内のファイルに以下のような形で記載しているので、この `todo!()` 部分を修正することになります。

```rust
/// # No. 04 入力と計算
///
/// 整数値を入力させ、その入力値を3倍した計算結果を表示するプログラムを作成せよ。
///
/// ---
///
/// 表示内容を返すように改変
///
/// - 引数: 整数値の文字列参照
/// - 戻り値: 整数値に 3 足した結果
pub fn knock_04(a: &str) -> i32 {
    todo!()
}
```

最初はコメントを丁寧に書いていたのですが、徐々に疲れが見え始めてくるのでご容赦ください。

## 終わりに

私はまだまだ Rust 学習中の身です。
一応自分で全テストは通していますが、私の解答は参考にならない（良い実装ではない）かもしれないのでご注意を！
テスト内容とかコメントにも結構適当な部分があるのでご注意を！

Rust はこれからどんどん重要度を増していく言語だと思っています。
この 100 本ノックを通して誰かがほんの少しでも Rust 力を高めてくれたら嬉しい限りです。
（そしてテストを通したコードを何らかの手段で公開して見せてくれるとさらに嬉しいです。参考にします。むしろそっちが目的かもしれない）

あと実装をしていて思ったのが、Rust を学習するならもっと Rust に最適化された 100 本ノックが必要だな、という点です。
もし機会があればそういったものも作ってみたいと思っています。
Raspberry Pi 4Bカメラの使い方の詳細チュートリアル（写真+ビデオ+モニター）

今回は、Raspberry Pi開発ボードにRaspberry Piカメラをインストールしてから使用するまでの流れを紹介します。 ブロガーは多くの記事を参照し、より包括的なプロセスを共有します。

### 配線接続
ハードウェアを接続する際には、まずRaspberry Pi Camera FFCケーブルを使ってRaspberry PiカメラとRaspberry Pi開発ボードを接続する必要があります。 ケーブルが接続するインターフェースは、CSI（Camera Serial Interface）インターフェースと呼ばれます。

Raspberry PiボードのCSIインターフェースは、USBポートとイーサネットポートの隣にあります。 まず、CSIコネクタの黒い板を抜き、ケーブルの青い方の端をイーサネット・コネクタの方向に差し込み、黒い板を押して固定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814122/64c0cbed-7b6d-97b3-dac5-0913603af4a7.png)


カメラ側も同じように接続し、ケーブルの青い方の端をカメラの背面と同じ方向に向けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814122/a9422f32-4f24-95f2-f5c1-f13175669039.png)


配線の際は、必ず電源を切った状態で行ってください。

### カメラ機能の起動
まず、Raspberry Piのターミナルを開き、Raspberry Piをアップデートする：
```
sudo apt-get update
sudo apt-get upgrade
```

その後、コマンドを入力してRaspberry Piの設定画面を開く：
```
sudo raspi-config
```

 以下の手順でカメラオプションを探し、Raspberry Piのカメラ機能をオンにします。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814122/2522a03d-7dc9-ce25-7eba-54af4679e145.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814122/ee2cd27e-2db9-ef98-1223-e1cedcfc21bc.png)

公式のRaspberry Piモジュールに加えて、サードパーティのモジュールでも試すことができる。私は https://www.sinoseen.com 用过一款还不错。
FileMarkerのデータをTypeScriptで

## FileMarkerのデータをTypeScriptで扱う

FileMarkerで作られたシステムがあり、その中のデータを使ったちょっとした別画面が欲しいが
FileMarkerを扱える人が既におらず、TypeScriptで開発することになった。

FileMarkerサーバーからデータを取得するのに
FileMaker Data API

https://help.claris.com/archive/docs/18/ja/dataapi

なるものがあるので、これで扱える。
なんらなライブラリがある。

https://www.npmjs.com/package/fm-data-api-client


## 取り扱い注意

それでもいくつか注意点
ライブラリの説明にも書いてあるけど
日付がMM/dd/yyyyの文字列になっているのでDateで扱う場合は、変換しないといけない

***
さらに画像を張り付けていたりすると

> オブジェクトフィールドデータの場合、FileMaker Data API はオブジェクトデータのオブジェクトへのパス参照を含む URL を返します。

とのことで、URLが乗っている
こんなの
```
https://192.168.1.2/Streaming/Additional_1/3B321F4E7FB47F7BF6A1BC583B321F829DFE36A23DFE36A16F12A7DD2F9AD9FE.jpg?RCType=EmbeddedRCFileProcessor
```
このURLで直接取得ではなく、302でリダイレクトされる。
しかし、axios.get()やfetch()だと上手く取得でない。

理由は
302レスポンスとともにSet-Cookieが来ていて、
リダイレクト先にCookieを送らないといけないが
その動作に対応していない。

こんな風に雑対処したんだけど、

```typescript
const ret = await axios.get(imageURL,{"maxRedirects":0,"params":prams,"responseType":"arraybuffer"});
if(ret.response.status === 302){
    const cookie = ret.response.headers['set-cookie'];
    const loc = new URL(`${imageURL.origin}${ret.response.headers['location']}`);
    let prams = {};
    for(const [key,val] of imageURL.searchParams.entries()){
        prams = Object.assign(prams,{[key]:val});
    }
    if(cookie && loc){
        return await axios.get(`${loc.origin}${loc.pathname}`,{"maxRedirects":0,"headers":{"Cookie":cookie.split(';')[0]},"params":prams,"responseType":"arraybuffer"});
     }
}
```

改めて調べたら、もっと簡単に出来る模様

https://qiita.com/takagimeow/items/2853f43a17e3367dd53c



## 余談

現在はそのFileMarkerで作られたシステムをリプレースすることになっている。
FileMaker Data APIを扱う機会は多分もうない。

## 余談2

FileMaker Data APIはオンプレで動かしているサーバーなのに、
なぜか、通信容量制限がある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1667610/228fcd4d-b410-83f1-648b-88ab6431e3d1.png)
PythonでPDFを簡単にCSVに変換

データのアクセス性と操作性は、データ管理の中心要素です。PDF形式は、クロスプラットフォームの互換性とレイアウトの固定性から、特にレポートや調査結果の保存に適しており、ドキュメントの共有や印刷に優れています。しかし、PDFの非構造化特性はデータ分析の分野での利用を制限しています。対照的に、CSV形式はシンプルで汎用性があり、処理が容易なため、データ操作と分析に最適です。
PDFをCSVに変換することで、データの実用価値が大幅に向上します。Pythonはこの変換タスクを効率的に実行できる強力なプログラミング言語です。本記事では、**Pythonを使用してPDFからCSVへの変換を実現する方法**について紹介します。

- **[PDFの表データをCSVファイルに変換する方法](#pdfの表データをcsvファイルに変換する方法)**

この記事で使用する方法は、[Spire.PDF for Python](https://www.e-iceblue.com/Download/Spire-PDF-Python.html)を使用します。PyPIからインストールします：`pip install Spire.PDF`。

## PDFの表データをCSVファイルに変換する方法
CSVは構造化された汎用の表データ形式として、シンプルな形式で表データを保存できますが、複雑な書式設定や表情報はサポートしていません。そのため、PDFドキュメントをCSVファイルに変換する際には、通常PDFドキュメント内の表のみをCSVファイルに変換します。
ライブラリには、PDFドキュメントの表抽出操作を処理するための`PdfTableExtractor`クラスが提供されており、個々のセルのテキストを取得することもできます。これらのクラスとメソッドを使用して、PDFの表をCSVファイルに変換することができます。
操作手順は以下の通りです：
1. 必要なモジュールをインポートします。
2. `PdfDocument`オブジェクトを作成します。
3. `PdfDocument.LoadFromFile()`メソッドを使用してPDFドキュメントを読み込みます。
4. 表抽出操作を処理するために`PdfTableExtractor`オブジェクトを作成します。
5. PDFドキュメントのページをループします：
    - `PdfTableExtractor.ExtractTable()`メソッドを使用して、現在のページの表をコレクションとして抽出します。
    - コレクション内の表をループします：
        - 表の行数と列数を取得します。
        - 表の行と列をループします：
            - `Utilities_PdfTable.GetText()`メソッドを使用してセルデータを取得します。
            - データをリストとして保存します。
    - csvモジュールを使用して、表データをCSVファイルに書き込みます。
6. リソースを解放します。

**コード例**
```python
from spire.pdf import PdfDocument, PdfTableExtractor
import csv

# PdfDocumentのインスタンスを作成
pdf = PdfDocument()

# PDFドキュメントをロード
pdf.LoadFromFile("Sample.pdf")

# PdfTableExtractorのインスタンスを作成
extractor = PdfTableExtractor(pdf)

# PDFドキュメントのページを反復処理
for i in range(pdf.Pages.Count):
    # 現在のページからテーブルを抽出
    tables = extractor.ExtractTable(i)
    # テーブルを反復処理
    for j in range(len(tables)):
        table = tables[j]
        tableData = []
        # 行数と列数を取得
        rowCount = table.GetRowCount()
        colCount = table.GetColumnCount()
        # 行と列を反復処理
        for row in range(rowCount):
            rowData = []
            for col in range(colCount):
                # セルのテキストを取得
                text = table.GetText(row, col)
                text = text.replace("\n", "").replace("\r", "")
                rowData.append(text)
            tableData.append(rowData)
        # テーブルデータをCSVファイルに保存
        with open(f"output/Tables/Table{i+1}_{j+1}.csv", "w", newline="", encoding="utf-8") as csvFile:
            writer = csv.writer(csvFile)
            writer.writerows(tableData)

# リソースを解放
pdf.Dispose()
```

変換結果
![PythonでPDFをCSVに変換](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/792f5fe7-c395-2a4a-0024-9dd2bea9379b.png)

本記事では、Spire.PDF for Pythonを使用してPDFの表をCSVファイルに変換する方法を紹介しました。

より多くのPDFドキュメント処理のテクニックについては、[Spire.PDF for Pythonのチュートリアル](https://www.e-iceblue.com/Tutorials/Python/Spire.PDF-for-Python/Program-Guide/Spire.PDF-for-Python-Program-Guide-Content.html)をご覧ください。
アプリケーションを安全にするために気をつけること

Webサービスを作成し運用し続けるには、セキュリティに対する意識が必要です。

ユーザーの情報が漏洩したり、攻撃を受けるなどしてサービスが存続できなくなるリスクがあります。

この記事では、Webサービスを作成する際に気をつけるべきセキュリティ対策について紹介します。

## ユーザーのインプットに対するエスケープ

ユーザーからの入力値をそのまま表示すると、クロスサイトスクリプティング（XSS）攻撃を受ける可能性があります。

例えば以下のようなインプットをそのまま表示すると、HTMLタグがそのまま解釈されてしまい、意図しない動作を引き起こす可能性があります。

```html
<script>window.open('https://example.com');</script>
```

これを防ぐためには、ユーザーからの入力値をエスケープする必要があります。

具体的には `<` `>` `&` などの特殊文字をエスケープすることで、HTMLタグとして解釈されないようにします。

## SQLインジェクション

ユーザーのインプットに関してもう一つ気をつけるべき攻撃がSQLインジェクションです。

ユーザーからの入力値をそのままSQLクエリに埋め込むと、SQLインジェクション攻撃を受ける可能性があります。

例えば以下のようなSQLクエリを実行すると、ユーザーからの入力値によっては意図しない動作を引き起こす可能性があります。

```sql
SELECT * FROM users WHERE name = 'ユーザーからの入力値';
```

この入力値に対して `' OR 1=1; --` などを入力されると、SQLクエリが以下のようになり、全てのユーザー情報が取得されてしまいます。

```sql
SELECT * FROM users WHERE name = '' OR 1=1; --';
```

これを回避するためにはSQLを実行する際にはプレースホルダーを使い、ユーザーからの入力値を直接SQL埋め込まないようにします。

```sql
SELECT * FROM users WHERE name = ?;
```

:::note info
AWS WAFなどのSaaSを使うことで、XSS, SQLインジェクション攻撃を防ぐこともできます。
:::

## DoS攻撃

ユーザーからの悪意ある操作の他に、DoS（Denial of Service）攻撃を受ける可能性もあります。

DoS攻撃は、サービス提供者の意図に反してサービスを利用できなくする攻撃で、大量のリクエストを送信したり大容量ファイルをアップロードするなどでサービスをダウンさせることがあります。

アップロードされるファイルの容量制限や、リクエストの制限などを設けることで、DoS攻撃に対する対策を行うことができます。

## まとめ

SaaSを使うことでセキュリティ対策を簡単に行うこともできますが、Webサービスを構築する際にはコードでもセキュリティに対する意識が必要です。

長くサービスを提供し続けるためにも、セキュリティ対策を怠らないようにしましょう。
Navigation2でplannerがdoes not existだった話

こんにちは。
kazuです。
navigation2でいい感じのプランナーを見つけて公式サイトのやつコピペしてエラーが出たのでメモしておきます。


nav.yaml
```yaml
planner_server:
  ros__parameters:
    planner_plugins: ["GridBased"]
    use_sim_time: True

    GridBased:
      plugin: "nav2_smac_planner::SmacPlannerLattice" 
      ...
```

# ERROR!!
`Failed to create global planner. Exception: According to the loaded plugin descriptions the class nav2_smac_planner::SmacPlannerLattice with base class type nav2_core::GlobalPlanner does not exist.`

# 解決
Iron以降では :: の代わりに / を使うらしいです。
`plugin: "nav2_smac_planner::SmacPlannerLattice" `
↓
`plugin: "nav2_smac_planner/SmacPlannerLattice" `

謎の仕様変更があるなら公式ページも変えてほしいですね
初心者向けPythonチュートリアル：数当てゲームで条件分岐と繰り返しを学ぶ

# 数当てゲームで条件分岐と繰り返しを学ぶ

今回は、Pythonを使って簡単なゲームを紹介します。テキストベースの「数当てゲーム」を作成してみましょう。
Pythonの基本的な機能を利用して、ランダムに選ばれた数をユーザーが当てるゲームを作成します。

## 準備
まずはPythonをインストールします。
Pythonがインストールされているか確認するには、ターミナル（コマンドプロンプト）を開き、以下のコマンドを入力してください。

```sh
python --version
```
バージョンが表示されればOKです。

## 数当てゲームのコード
以下が数当てゲームのコードです。テキストエディタにコピーして保存し、実行してみてください。

```python
import random

def guess_the_number():
    print("数当てゲームへようこそ！")
    print("1から100までの数を当ててください。")

    number_to_guess = random.randint(1, 100)
    number_of_guesses = 0

    while True:
        guess = input("あなたの予想を入力してください: ")
        
        try:
            guess = int(guess)
        except ValueError:
            print("無効な入力です。数値を入力してください。")
            continue
        
        number_of_guesses += 1
        
        if guess < number_to_guess:
            print("もっと大きい数です。")
        elif guess > number_to_guess:
            print("もっと小さい数です。")
        else:
            print(f"おめでとうございます！{number_of_guesses}回目で当たりました！")
            break

guess_the_number()
```

## コードの説明
### 1.ライブラリのインポート:
```python
import random
```
randomライブラリを使用して、ランダムな数を生成します。

### 2.ゲームの開始:
```python
def guess_the_number():
    print("数当てゲームへようこそ！")
    print("1から100までの数を当ててください。")
```
ゲームの開始メッセージを表示します。

### 3.ランダムな数の生成:
```python
number_to_guess = random.randint(1, 100)
number_of_guesses = 0
```
1から100までのランダムな数を生成し、変数number_of_guessesで予想回数をカウントします。

### 4. ユーザーの入力を処理:
```python
while True:
    guess = input("あなたの予想を入力してください: ")
    
    try:
        guess = int(guess)
    except ValueError:
        print("無効な入力です。数値を入力してください。")
        continue
```
ユーザーの入力を受け取り、整数に変換します。無効な入力があれば再度入力を促します。

### 5.予想を評価:
```python
number_of_guesses += 1
    
if guess < number_to_guess:
    print("もっと大きい数です。")
elif guess > number_to_guess:
    print("もっと小さい数です。")
else:
    print(f"おめでとうございます！{number_of_guesses}回目で当たりました！")
    break
```
ユーザーの予想が正解かどうかを評価し、正解であれば終了します。

## まとめ
この数当てゲームは、Pythonの基本的な機能を学ぶのに最適です。
数当てゲームを元に、さらに機能を追加して自分だけのオリジナルゲームを作ってみてください。

質問やフィードバックがあればコメント欄にお願いします。Happy Coding!
【HTML】サーバを立てずに、HTMLで同じディレクトリにあるJSONを読み込む

どうもこんにちは。

今回は、**サーバを立てずにHTMLで同じディレクトリにあるJSONを読み込む**のに苦戦したので、その方法をメモとして残します。

# とりあえずHTMLで雛形作る

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Sample</title>
    </head>
    <body>
        <div class="box">
            <div class="display-area">
                <div>
                    名前: <span id="name-area"></span>
                </div>
                <div>
                    年齢: <span id="age-area"></span>
                </div>
            </div>
        </div>
        
        <script>
            const source_src = 'sample_data.json';
            var xhr = new XMLHttpRequest();
            xhr.open('GET', source_src, false);
            xhr.send(null); // ココ！！
            
            if (xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);

                var nameAreaElement = document.querySelector('.name-area');
                var ageAreaElement = document.querySelector('.age-area');

                nameAreaElement.textContent = data["datas"]["name"];
                ageAreaElement.textContent = data["datas"]["age"];
            }
        </script>
    </body>
</html>
```

# サンプルJSON

```json:sample_data.json
"datas": {
    "name": "テスト太郎",
    "age": 30
}
```

# さてさて...

作成した`index.html`のファイルパスをコピーしてブラウザで開くと...

```text
Access to XMLHttpRequest at 'file:///Users/testuser01/sample.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, isolated-app, brave, https, chrome-untrusted, data, chrome-extension, chrome.
```

CORS制約に反していると言われてしまいます。

# 解決するには

以下のコマンドをターミナルで実行します。

```sh
# windowsの場合
chrome.exe --disable-web-security --user-data-dir="C:/ChromeDevSession"

# macの場合
open -na "Google Chrome" --args --disable-web-security --user-data-dir="$HOME/ChromeDevSession"
```

:::note warn
必ず、Chromeを完全に終了した状態で実行してください。
バックグラウンドで動いている状態では実行できないので、注意してください。
:::

これでJSONファイルを読み込むことができるようになります。
prismaとsupabaseの連携仕様が変わった

# 以前までのschema.prismaでは
```schema.prisma
datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
}
```

```.env
DATABASE_URL=postgres://[db-user].[project-ref]:[db-password]@aws-0-[aws-region].pooler.supabase.com:6543/[db-name]?pgbouncer=true&connection_limit=1
```

# 今回の仕様
```schema.prisma
datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
  directUrl         = env("DIRECT_URL")
}
```

```.env
DATABASE_URL=postgres://[db-user].[project-ref]:[db-password]@aws-0-[aws-region].pooler.supabase.com:6543/[db-name]?pgbouncer=true&connection_limit=1

DIRECT_URL=postgres://[db-user].[project-ref]:[db-password]@aws-0-[aws-region].pooler.supabase.com:5432/[db-name]
```

# 変更点
directUrlが必要になった。
prismaとデータベースの接続に関しては環境変数に原因があるので[db-user]、[project-ref]、[aws-region]、[db-name]の部分を見直してみてください。

詳しくは
https://supabase.com/partners/integrations/prisma
Flutter on the Webだけどnpm-watchで自動ファイル更新

# Hi! 自動化大好きおじさんデス!

最近Flutterでwebゲームアプリ開発して遊んでいるのですがassetsのファイルを書き換えた際の待ち時間が、メンドクセェ_:(´ཀ`」 ∠):_

dartファイルは更新したらホットリロードされるのにassets下はbuildしないと更新されません。Releaseの時は思わぬ事故を防げるのでいいのですが、Debugでこれだと開発が大変です。

解決方法として、`assets`下の更新されたファイルを`build/flutter_assets/assets`下にコピーする、という手段があります。

```
> xcopy assets\\* build\\flutter_assets\\assets /d /s /y /f
```

最初はこれをバッチファイル化して運用しようとしてたのですが、メンドクセェ_:(´ཀ`」 ∠):_

ファイル更新の度にいちいちバッチファイル叩きたくないでござる、ゼッタイに叩きたくないでござる！

Node.jsで開発しているとこの辺だいたい自動化されているので、裏山……いやNode使えばいいのか🤔

nodemon自体は単体で動くプログラムなのでnpmが入ってる環境ならpackage.json書いてしまえば動きます。ただnodemonそのまま設定を書くのはメンドクサイので今回はnpm-watchを使ってみました。

# インストール

npmのプロジェクトじゃないのでグローバルに入れてしまいます

```
> npm install -g npm-watch
```

# 設定ファイルの記述

プロジェクトフォルダ直下にpackage.jsonを置きました

```json:package.json
{
    "watch":{
        "assets_data": {
            "patterns": ["assets/data"],
            "extensions": "*"
        }
    },
    "scripts": {
      "assets_data": "xcopy assets\\data\\* build\\flutter_assets\\assets\\data /d /s /y /f",
      "watch": "chcp 65001 && npm-watch"
    }
}
```
### package.jsonの解説

scriptsの中が`npm run`から見えるコマンドになります。起動に`npm run watch`としてscriptsの中のwatchに設定されたコマンドを実行します。

xcopyがコンソールのキャラセット(デフォルトcp932)でメッセージを出しちゃうので、`chcp 65001`を付けておくとnpmさんが期待するutf8でメッセージを出すようになります。その後続けて先ほどインストールしたnpm-watchコマンドが実行されます。

npm-watchコマンドはjsonのwatchの中をパラメータとして認識します。この中に実行したいscriptsのエントリ名(scriptsの中のassets_dataに書かれたコマンドを実行したいのでassets_data)のエントリを作成します。

最低限patterns(ウォッチ対象とするディレクトリ名。サブディレクトリも見てくれる)とextensions(ウォッチ対象とするファイルの拡張子を指定。全て対象なら\*)があればいいです。

これで
1. npm run watchでscripts中のwatchの行が実行される
1. scriptsの上にあるwatchのパラメータでnpm-watchがウォッチ開始
1. 更新を検知したらscriptsのassets_dataの行を実行

となり、自動でファイルが更新されます。

### xcopyなの？ robocopyは使わないの？

robocopyはプロセスが起動したまま先に実行し終わっちゃってnodemonがええのんか？ってエラーを出す(問題はないようですが)ので、xcopyを使うようにしました_(:3」∠)_


# 実行

package.jsonを置いた場所で実行します。というより実行する場所にpackage.jsonをおきましょう、と

```
> npm run watch
```


# ファイルを更新してみる

自動で更新されたら成功です

```
[assets_data] [nodemon] restarting due to changes...
[assets_data] [nodemon] starting `npm.cmd run --silent assets_data`
[assets_data] C:\tmp\FlutterGame1\assets\data\event.toml -> C:\tmp\FlutterGame1\build\flutter_assets\assets\data\event.toml
[assets_data] 1 File(s) copied
[assets_data] [nodemon] clean exit - waiting for changes before restart
```

# Flutterでの注意点

ファイルはちゃんと変わるのですが、APIが至る所でキャッシュします。

例えばloadStringの場合は次のように`cache: false`を付けてキャッシュされないようにしないと反映されません。

```dart
    self.data = TomlDocument.parse(
            await rootBundle.loadString("assets/data/event.toml", cache: false))
        .toMap();
```

cacheコントロールができないAPIならもうブラウザの更新ボタンしかなく(´・ω:;.:... 

それでも、それでもbuildよりは圧倒的に速い！