OmniAICreator/Qiita-1.07M huggingface dataset:
結局php-fpmってなに？

# FPMってなんの略
たぶん初学者がPHPでなにか開発したい、そうだdockerだとなったときにまず出てくるこの言葉。いったいなんなのでしょうか。よくわからずに使ってる人も多いとは思いますが、最初にこのFPMとはなんの略でしょうか。

``FastCGI Process Manager``

そこで疑問が、FastCGIって何？

# そもそもCGIって何？
```Common Gateway Interface```

HTMLはあくまで文書です。ですので動的に画面上の数字を変えたり表示される画像を変更したりはHTML単体ではできません。HTML単体ではできることはだいぶ限られてきます。

やっぱり動的にやりたいじゃないですか。商品をカートに追加するとか、購入ボタンぽちーって押したら購入完了になるとか。

そこで登場するのがCGI
込み入った話はともかくとして、WEBサーバ側から何かしらのプログラムを呼び出して実行した結果を返す仕組みのことをCGIといいます。

# ただCGIには問題があって・・・
1. リクエストごとにCGIプロセスを立ち上げる必要がある
    1. つまり毎回CPUリソースを消費するんです
    1. プロセス立ち上げるのは思ってる以上にCPUリソースが必要なのです
1. アクセス数が多いサイトだと、CPU負荷が上がって死ねる
1. apacheモジュールだと、apacheの再起動するときに気をつけないといけない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585901/e81df8c7-7708-460e-646c-b0c9473e98f3.png)


# この問題を解決したのがFastCGIです
FastCGIでは
1. FastCGIサーバがCGIプロセスを管理する
1. プロセスを毎回立ち上げない
1. 一度上げたプロセスはしばらくおいておく
1. プロセス立ち上げの負荷が抑えられる
1. WEBサーバと分離してるのでアプリケーションサーバだけ立ち上げ直すということが可能

WEBサーバとは別のFastCGIサーバ(アプリケーションサーバ)がこの管理をするのでWEBサーバはユーザからのアクセス処理に専念できるというわけです。

# FPMはFastCGIのPHP実装ということです
つまるところFPMはphpをFastCGIとして実行するためのアプリケーションサーバということです。
phpはスレッドではなくてプロセスで処理されるためにCGIの仕組みそのものと言っても過言ではないですが、このphp-fpmを利用することでまるでマルチスレッドで動いているかのような振る舞いができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585901/189c0ce1-7ff9-d3c5-0fe2-05422cdda3ad.png)


# 終わりに
FPMは高負荷時で普通にやったら遅くなるPHPを効率よく実行するための仕組みです。
【自分用メモ】組込みで大事なメモリに関するまとめ

# メモリ
組込みのお仕事で避けて通れないメモリの概念関するメモ。

## メモリとは
参考： [【マイコン】メモリの概念「メモリ空間」「メモリマップ」とは？CPUとメモリの関係について解説！](https://practicalelectronicsblog.com/memorymap)

# メモリ領域
## メモリ領域とは
参考：https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/MP/final/part06/node8.html
### 静的領域
* staticをつけると静的領域に配置される

参考：[【C言語】staticローカル変数の使い方・メリットを解説-静的領域に配置される効果](https://daeudaeu.com/c-static-local/#82208221)
参考：https://monozukuri-c.com/langc-static-memory/

### 動的領域
* 自動変数が確保される領域

参考：https://qiita.com/LemniscaterN/items/a3abfa143612cb928bde
参考：https://monozukuri-c.com/langc-dynamic-memory/
参考：https://www.youtube.com/watch?v=_qZ_vZYEfq0

##### スタック領域
* コンパイラやOSが割り当てる
* 使用容量は、コンパイル、リンク時に決まる
* メモリの管理が、下から順に積み上げていく先入れ先出し法

参考：[スタックについて勉強したのでまとめてみた](https://qiita.com/uchidak/items/8efbf0190fb7f1e9bdda)
参考：[スタックってなあに？ - プログラミング上の注意](https://uquest.tktk.co.jp/embedded/learning/lecture07-2.html)
参考：[スタック使用量見積もり/調査](https://tonkun-tech.com/stack_estimate/)
参考：[使用スタックサイズを求める - その1：gccの機能を活用する。](https://qiita.com/developer-kikikaikai/items/ae134d3320b016c7ef18#その1-gccの機能を活用する)
参考：[.suファイルとはスタック領域を確認するファイル](https://malt.zendesk.com/hc/ja/articles/900006257803-Nios-II-SBT-for-Eclipse-%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B-su-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB-%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%B5%E3%82%A4%E3%82%BA%E7%A2%BA%E8%AA%8D%E7%94%A8-%E3%81%AE%E7%94%9F%E6%88%90%E6%96%B9%E6%B3%95#h_374b6d9d-523c-465d-8b80-d83e93e1d5fd)


##### ヒープ領域
* ソフトウェアで割り当てる（malloc、free、ガベージコレクション）


参考：[ヒープとスタック](https://uquest.tktk.co.jp/embedded/learning/lecture16.html)

### 静的領域
編集中。
自社メールサーバー構築時の重要論点

XServerでメール配信のシステム構築中。やけにBounceしているらしい。ただ、色々調べてみるとGoogle以外では特にBlack認定されているわけではないらしい。ということで、SPF、DKIM、DMARCあたりの設定不足が問題かと思われる。

世界中のAbuse機関でそのドメインがどのように判定されているかは以下URLで確認可能。

https://whatismyipaddress.com/blacklist-check


以下、メモ。
---


自社ドメインからの一斉送信メールを安全に配信するためには、受信側のメールサーバに対して「正当な送信者」であることを証明する必要がある。そのために、SPF（Sender Policy Framework）、DKIM（DomainKeys Identified Mail）、DMARC（Domain-based Message Authentication, Reporting, and Conformance）の設定が不可欠である。これらは、電子メールの偽装を防ぎ、信頼性と配信能力を高めるために設計されたものである。XserverにSMTPサーバを設置し、一斉送信メールシステムを構築する際には、以下の点に注意を払う必要がある。

### SPFレコードの設定

- **設定内容**: 自社のドメインのDNS設定に、メール送信を許可するIPアドレスを含むSPFレコードを追加することが求められる。これにより、そのドメインから送られるメールの正当性が受信側のメールサーバによって検証されることになる。
- **具体例**: `v=spf1 ip4:送信サーバのIP -all` のように設定する。ここで、「-all」は、指定されたIPアドレス以外からのメールを拒否する強いポリシーを意味する。

### DKIMの設定

- **公開鍵/秘密鍵の生成**: DKIMでは、公開鍵をDNSレコードに追加し、秘密鍵でメールを署名する。受信サーバは公開鍵を使用して署名を検証し、メールの改ざんがないことを確認する。
- **DNSレコードの追加**: DKIMの公開鍵を自社ドメインのDNSに追加することが要求される。これにより、受信者側でDKIM署名の検証が可能になる。

### DMARCの設定

- **ポリシーの定義**: DMARCレコードをDNSに追加することで、SPFとDKIMの検証結果に基づいて、メールの取り扱い方を指示することができる。これにより、ドメイン名の悪用をさらに防ぐことが可能となる。
- **報告の設定**: DMARCポリシーでは、検証失敗時に報告を送信する設定も可能である。これにより、配信問題を迅速に特定し、対処することができる。

### その他の考慮事項

- **送信量の管理**: 一斉送信によるメール量が急激に増加すると、スパムとみなされる可能性がある。送信頻度や量を適切に管理することが求められる。
- **リストのメンテナンス**: バウンスメールや非アクティブなアドレスを定期的にリストから削除し、高いエンゲージメントを維持することが重要である。
- **内容の最適化**: スパムフィルタを回避するために、メールの内容や構造（例えば、大量のリンクやスパムに関連する単語の使用を避ける）も検討する必要がある。

正しく設定されたSPF、DKIM、DMARCは、メール配信の信頼性を高めるだけでなく、受信者に対する自社のブランドの信頼性を保つためにも重要である。これらの技術の適切な利用は、メールキャンペーンの成功に直接影響を与えるため、設定時には細心の注意を払う必要がある。
デイリースクラムを進捗報告会にさせないためにやったこと

こんにちは、アジャイル4年生のFRです！

皆さんはデイリースクラム[^1]や朝会を疎かにしていませんか？

毎回同じ報告をする、他人の進捗に興味を持たない、困っていることはあるかと聞かれても毎回思いつかない・・・

このような日々を続けているとデイリースクラムが形骸化していき、毎朝の数十分がムダな時間になってしまいます。



そうならないためにどうすればいいの・・・

この記事は実際の経験を基に「デイリースクラムを進捗報告会にさせないために」やったことを紹介します！

アジャイル未経験者や、デイリースクラムの場を改善したい方に刺さってくれると嬉しいです！



# 1.ファシリを持ち回り制にする

同じ人が同じように仕切ることで飽きが発生してしまうことが形骸化の第一歩です。

また、ファシリ以外の人は自分の進捗発表だけになってしまうので、他人の進捗に関心が無くなってしまいます。

ファシリを持ち回りにすることでデイリースクラムに新鮮さを出すことができ、当事者意識を持たせることもできます。



# 2.時間と話すことを決める

デイリースクラムは必ず15分以内に終わらせること。また、以下の3点を話すことをルール化しました。

- 昨日やったこと
- 今日やること
- 困っていること
  - 特に困っていることが無い場合はスキップ。また、話が長くなりそうな場合はデイリースクラム後に必要な人を集めて別途会議体を設けることもルールに入れました。

「困っていること」って具体的に何を言えばいいの？という人もいるかもしれません。私のチームは以下のようなことを上げていました！

- チームメンバーが技術的な問題に直面している場合
  - 例：「昨日、thymeleafで条件分岐の実装をしていたがtrue判定がされず困っている。知見がある方に見てほしい。」
- チームメンバーが必要なスキルやリソースを持っておらず、進捗に影響を及ぼす可能性がある場合
  - 例：「バッチのスケジュールを変更しようと考えているが、変更方法がわからないためインフラ担当に協力してもらいたい。」
- できるタスクが全て完了してしまい、自身の判断で次のタスクに着手できないような場合
  - 例：「次のタスクのReady条件がまだ満たされていないため、手持ち無沙汰である。何か手伝えることはないか？」
- 他の関係者とのコミュニケーションに問題がある場合
  - 例：「A社側の先行タスクが終了していないため、まだ私のストーリーの着手ができていない。遅延する可能性の有無を確認したいが、誰に聞くべきか？」



# 3.事前に話す内容を書く

これが私の中で一番効果があると思います！

wikiなど開発メンバー全員が確認できるものにデイリースクラム用のページを作り、デイリースクラムが始まるまでに話す内容を書くようにしていました。

相手の報告を聞くだけでは情報が伝わりづらいので聞き流してしまうことが多かったのですが、目＋耳の情報で理解度が大幅に上がりました。

また、話す内容を事前に書いておくことで発表する際に言いたいことをまとめやすくなりました！

話す内容を毎日記録することで自分が何をやっていたか後から確認がしやすいので、ふりかえり[^2]にも活用できます！

参考までに、私たちのチームの記載例を載せておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1250958/3b3314ff-7d7b-91a6-1538-1eb6e0399923.png)




# 4.メンバーのテンションが低いときは最近の出来事なども話す

週明けや緊急の不具合対応後などメンバーの空気が全体的に重いと感じた時はいつもの報告＋最近起きた出来事などを10秒程度で発表してもらうようにしました。

アイスブレイクも兼ねることで場を温め、困っていることや相談事が言いやすくなりました。



# 5.バーンダウンチャートを確認する

全員の進捗報告が終わった後にバーンダウンチャート[^3]の確認を行っていました。

これを毎朝確認することで遅延が発生していることに気が付けます。

また、誰が遅延しているタスクのサポートに回るかなどの判断もその場で行うことが出来ます。



# 6.QAの時間を設ける

デイリースクラムを終わらせる前に仕様やアジャイルのこと、コードの質問までなんでも聞いていい時間を設けました。

人の入れ替わりが多い現場だったこともあり、新しく入ってきた方が困らないようとにかく質問しやすい空気作りを心掛けました！



# 7.最後に

デイリースクラムは毎朝行うため、形骸化しやすいものです。

デイリースクラムを疎かにするとコミュニケーションの質が下がり、手戻りや考慮漏れが発生する可能性があります。

スプリントゴール[^4]を達成させるための大事なスクラムイベントの一つだということを心掛けていきましょう！


[^1]: デイリースクラム
スクラムイベントの一つです。チーム全体の進捗把握や、課題や障害を特定し、プロジェクトの進行に影響を及ぼす要因を見つけ出すことを目的としています。課題や障害に対して素早い調整をチーム全体で行うことで、プロジェクトの成功に繋がっていきます。<br>

[^2]: ふりかえり(レトロスペクティブ)
スプリント終了時に行うスクラムイベントです。チーム全体で今回のスプリントで起きた出来事をふりかえっていきます。私のチームはKeep(良かったこと),Problem(課題),Try(改善策)という3つの観点でふりかえりを行っていました。<br>

[^3]: バーンダウンチャート
プロジェクトの進捗状況をグラフで表現したものです。実践線・計画線・理想線の3つの線を見比べることで進捗に遅れが出ているかを確認することが出来ます。<br>

[^4]: スプリントゴール
今回のスプリントの目的を短い文章でまとめたものです。スプリントゴールがあることで、そのゴールに到達するためにどのような作業を行うべきか検討しやすくなります。スプリントゴールは新しいスプリントを始める時にチーム全体で決めていきます。<br>
【Flutter】Containerに影をつける

こんにちは。
今回は、Containerに影をつける方法を紹介します。

# 方法
![スクリーンショット 2023-08-13 20.18.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524120/ce57fee2-1687-ea40-8538-b4c8b9db94e0.png)

まず、BoxDecorationのプロパティにboxShadowを設定します。

次に、boxShadowのプロパティを設定します。
* color: 影の色の指定
* offset: オフセット、影の位置の指定
* offset のデフォルト値は、Offset(0, 0)※Widgetと影が、同じ位置で重なっている
* blurRadius: 影のぼかし具合を調節
* spreadRadius:影の広がり具合を調節


# 使用例
```dart

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Container(
            width: 100,
            height: 70,
            decoration: const BoxDecoration(
              boxShadow: [
                BoxShadow(
                  color: Colors.grey,
                  spreadRadius: 10,
                  blurRadius: 20,
                  offset: Offset(1, 1),
                ),
              ],
              color: Colors.white,
            ),
          ),
        ),
    );
  }
}

```

# 実行例
![スクリーンショット 2024-04-01 18.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524120/5096dff4-0c8a-24cf-bb6f-097ef2ddd888.png)

# 最後に
ここまで読んでいただき、ありがとうございました！
いいねしてくれたら、スキップして喜びます:heartpulse:
オープンソースのバグで開発が止まりかけたので、コミットを積んで直してみた話

去年、eKYCを利用した本人確認機能を開発していたのですが、導入しようとしたオープンソースにバグがあり、そのままでは開発が進められない事態となりました。
この状況を打破すべく、オープンソースにコミットを積んで運良く直すことができたので、背景や解決までに至ったプロセスなどを紹介します。

# 背景

ユーザーが本人確認申請時に個人情報をeKYCへ送信した後、正しくレスポンスが返ってきた場合はユーザーをeKYCの本人確認手続き画面へ誘導する必要がありました。送信するだけであれば同期処理で良いのですが、エラーのレスポンスが返された時にその場で再送信をユーザーへ促したかったことと、レスポンスが正しく返ってきた時はその場で本人確認手続き画面へ誘導させたかったため、なるべく、ユーザーの待ち時間が発生しないように非同期処理にしなければなりませんでした。

ただし、eKYCへの同時リクエスト数が5までであっためSidekiqで制御する必要がありました。

Sidekiq単体で制御を行う場合、有料版のSidekiq Enterpriseへ切り替えなければなりませんでした。しかし、利用料金が高額なことと開発期限が約1ヶ月後に迫っていたため、予算申請から導入までの時間も考えると開発負荷を上げてしまう懸念がありました。

そこで、オープンソースの[sidekiq-limit_fetch](https://github.com/deanpcmad/sidekiq-limit_fetch)というサードパーティを使うことで、同時リクエスト数を制限しようとしました。しかし、ここでも問題がありsidekiq-limit_fetchを実際に導入してみるとバグが存在していたためそのまま使用することが困難でした。

# 対応方針

sidekiq-limit_fetchで同時リクエスト数制限を実現させるためにできそうなことは、2つありました。

- Sidekiqをダウングレードする
- sidekiq-limit_fetchのバグを修正する

sidekiq-limit_fetchの導入時、開発環境に入っていたSidekiqのバージョンはv7でした。v7は半年以内にリリースされたばかりだったこともあり、sidekiq-limit_fetchがまだv7に完全に対応できていない状況でした。Sidekiqをv6にダウングレードするとエラーが出ずに動作することを確認できましたが、メジャーバージョンを下げることは影響範囲のことを考慮すると懸命でないと判断し、sidekiq-limit_fetchのバグ修正を優先させました。

# 発生していたバグ

問題となるsidekiq-limit_fetchのバグは2つありました。

- キューの設定が読み込めない
- Sidekiq::BasicFetchの引数に渡す値が間違えていた

# キューの設定が読み込めない

## 原因

`sidekiq.yml`に設定されているキューの名前を取得し`to_s`もしくは`to_sym`で型を変換する処理があったのですが、コード1のように、重みづけも設定されていると配列が取得されるため型の変換でエラーが起きていました。

**コード1**

```yaml
:queues:
  - ['high_priority', 2]
  - ['low_priority', 1]
```

**発生していたエラー**

```bash
undefined method `to_sym' for ["active_storage_analysis", 4]:Array
queue.process_limit = @process_limits[queue_name.to_s] || @process_limits[queue_name.to_sym]
                                                                                        ^^^^^^^
Did you mean?  to_s
               to_set
/Users/me/.rvm/gems/ruby-3.1.2@project/gems/sidekiq-limit_fetch-4.4.0/lib/sidekiq/limit_fetch/queues.rb:121:in `apply_process_limit_to_queue'
```

## 解決方法

キューを受け取った時に、文字列か配列かを判定して必ずキューの名前だけを取得する処理を入れました。

**実装した修正コミット**

```ruby
@queues = config[:queues].map do |queue|
  if queue.is_a? Array
    queue.first
  else
    queue
  end
end.uniq
```

GitHub**のコミット**

https://github.com/deanpcmad/sidekiq-limit_fetch/pull/155/commits/97084a6352bd4f51b6dd55232ffe557c71e98475

# Sidekiq::BasicFetchの引数に渡す値が間違えていた

## 原因

sidekiq-limit_fetchの処理で`Sidekiq::BasicFetch`にSidekiqのconfigを渡す処理があります。Sidekiq v6ではSidekiqモジュール内にconfigが含まれており、sidekiq-limit_fetchの実装もSidekiqモジュールからconfigを取得する実装になっていました。しかし、Sidekiq v7からconfigは別モジュールに切り出されてconfigの取得方法が変わったため、sidekiq-limit_fetchもSidekiqの変更に合わせて実装を変える必要がありました。

## 解決方法

Sidekiqのバージョンが7であるときは、Configモジュール経由でconfigを取得するようにしました。

**実装した修正コミット**

```ruby
Sidekiq::BasicFetch.new(post_7? ? Sidekiq.default_configuration.default_capsule : config).bulk_requeue(*args)
```

**GitHubのコミット**

https://github.com/deanpcmad/sidekiq-limit_fetch/pull/155/commits/b222c01b8aa1d921850121725fcc18b83afbac7c


# 修正してマージされたPR
https://github.com/deanpcmad/sidekiq-limit_fetch/pull/155
VS CodeでPythonの仮想環境を構築する(windows)

## Python仮想環境
Python3の標準ライブラリであるvenvを用いて仮想環境を構築します。
仮想環境はプロジェクトによってバージョン、ライブラリを使い分けるために使用します。

## venvで仮想環境を作成する
以下のコマンドを実行して仮想環境を作ります
```
python -m venv [仮想環境名]
```
このように、[仮想環境名]で付けた名前のフォルダが作成されます![スクリーンショット 2024-04-01 173735.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679771/0e4ca8cf-9e2f-3ddf-94e7-e869d4bfad51.png)

次に、仮想環境を起動します
```
[仮想環境名]/Scripts/activate
```
このコマンドを実行し、成功するとPowerShellの場合このように表示されます。
![スクリーンショット 2024-04-01 174546.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679771/49687ab3-d200-5f89-d8ba-d2159f174220.png)


仮想環境を抜けたい場合は次のコマンドを実行してください
```
deactivate
```

## インタープリターを指定する
venvで作成したpython.exeをインタープリターに指定しないと、ライブラリを読み込むことができません。

ctrl + shift + Pを押してコマンドパレットを開き、インタープリターを選択を押します
![スクリーンショット 2024-04-01 175853.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679771/5fff6b3d-11c4-6771-693f-fb52a1f09d56.png)

先ほど作成したものを選びます
![スクリーンショット 2024-04-01 175925.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679771/b28e3474-fdfa-3ac2-cf95-5593b5f152d1.png)

## その他
作成した仮想環境上のPythonのversionは作成コマンド実行時のversionと同じものになります。
変更したい場合はこちらの記事を参考にしてみてください。
https://dev.classmethod.jp/articles/change-venv-python-version/
Heroku環境でBypass RLSを実現する方法（activerecord-tenant-level-securityにモンキーパッチする）

Row Level Security(RLS)は、PostgreSQLが提供する機能で、接続ユーザーに対して操作できる行を制限できる機能で、１テーブルに複数の顧客データが混在するマルチテナントSaaSでは必須と言えるものです。

こういうRLSポリシーを設定すると......

```sql
CREATE POLICY tenant_policy ON tickets
  AS PERMISSIVE
  FOR ALL
  TO PUBLIC
  USING (tenant_id::text = current_setting('tenant_level_security.tenant_id')) -- SELECT文等に追加される条件式
  WITH CHECK (tenant_id::text = current_setting('tenant_level_security.tenant_id')) -- UPDATE文等に追加される条件式
```

SELECT文のWHERE句に`tenant_id`のチェックが強制的に追加され、テナントIDの行しかクエリ結果に含まれなくなります（指定しなければクエリ結果が空になる）。そのため、「テナントAのユーザーに、テナントBのチケットが見えちゃった！」といった情報漏洩を防ぐことができます。

なお、上記のポリシーは activerecord-tenant-level-security で設定されるポリシーです。Railsではactiverecord-tenant-level-securityを使えば簡単にRLSを利用できます。

https://github.com/kufu/activerecord-tenant-level-security

# テナント横断でアクセスするには？

ここで、上記のポリシーで追加される条件式では、テナントIDは1つしか指定できません（指定しなければクエリ結果が空になる）。

```sql
USING (tenant_id::text = current_setting('tenant_level_security.tenant_id')) -- SELECT文等に追加される条件式
```

なので、ETLなどでテナント横断でクエリしたい場合に困ります。

そのため、PostgreSQLでは`BYPASSRLS`属性を付けることでRLSを無視してクエリできます。

```sql
CREATE ROLE etl_user WITH BYPASSRLS; -- RLSを無視できるユーザー
```

# でもHerokuでは`BYPASSRLS`を設定できない！

**しかし**、Herokuでは`BYPASSRLS`が使えません。"Read-only", "Read and write"のような大雑把な設定しかできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179154/d4e08321-1c62-6663-fa52-1b7660687681.png)

# 解決法：ポリシーを書き換える

BYPASSRLSが使えないなら仕方ない、ポリシーを書き換えてなんとかするしかありません。

現在のユーザーが `etl_read` の時のみテナントIDのチェックをしないようにします。

```SQL
CREATE POLICY tenant_policy ON tickets
  AS PERMISSIVE
  FOR ALL
  TO PUBLIC
  USING (CURRENT_USER = 'etl_read' OR tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id', true), '')::#{tenant_id_data_type})
  WITH CHECK (CURRENT_USER = 'etl_read' OR tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id', true), '')::#{tenant_id_data_type})

```

本当は `CURRENT_USER = 'etl_read'`のようなby nameの指定はよくない（ユーザー名に依存しない方法にしたい）のですが、他に方法が思い浮かびませんでした！

# activerecord-tenant-level-security にモンキーパッチを当てる

では、activerecord-tenant-level-securityで上記のポリシーを使うにはどうすればいいのか？`TenantLevelSecurity::SchemaStatements::create_policy` でポリシーを設定しているので、それを書き換えればよろしい。

```ruby
# config/initializers/tenant_level_security.rb

module TenantLevelSecurity::SchemaStatements
  # TenantLevelSecurityにモンキーパッチして、ポリシーの条件式を変更
  #
  # 特定のユーザー（kickflow_read）でのみ全件取得可能にする
  # 本来はユーザーのBypassRLS属性を設定すべきだが、herokuではそれができないため
  def create_policy(table_name)
    execute <<~SQL.squish
      ALTER TABLE #{table_name} ENABLE ROW LEVEL SECURITY;
      ALTER TABLE #{table_name} FORCE ROW LEVEL SECURITY;
    SQL

    tenant_id_data_type = get_tenant_id_data_type(table_name)
    execute <<~SQL.squish
      CREATE POLICY tenant_policy ON #{table_name}
        AS PERMISSIVE
        FOR ALL
        TO PUBLIC
        USING (CURRENT_USER = 'kickflow_read' OR tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id', true), '')::#{tenant_id_data_type})
        WITH CHECK (CURRENT_USER = 'kickflow_read' OR tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id', true), '')::#{tenant_id_data_type})
    SQL
  end
end
```
#6 Playgroundから色調整を行い背景を透過させた画像（授業の練習作品）

```swift:
import UIKit

let practiceImage = UIImage(named: "practice.png")
let practiceImageView = UIImageView(image: practiceImage)
// practiceImageView.backgroundColor = UIColor(red: 255/255, green: 0, blue: 0, alpha: 1) // 背景色の変更

let patternImageView = UIImageView(image: UIImage(named: "pattern.png"))
patternImageView.frame = practiceImageView.frame
patternImageView.addSubview(practiceImageView)
```

このコードは以下の処理を行っています：

画像の読み込み：UIImage(named:)メソッドを使用して、"practice.png"という名前の画像ファイルをロードします。
    
画像ビューの作成：読み込んだ画像でUIImageViewを作成します。
    
背景色の変更：画像ビューの背景色を変更します。
    
別の画像ビューの作成：今回は"pattern.png"という別の画像を読み込み、それを新しいUIImageViewの内容として設定します。
    
ビューのフレーム設定：2つ目の画像ビューのフレームを最初のものと同じに設定します。一つのビューを別のビューに追加：最初の画像ビューを2つ目の画像ビューに追加します。

以下の画像は、様々な髪色のハクを示しています：

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3703986/f55c4de3-9481-c043-4f32-be4e6f685036.png)

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3703986/035b6c93-7931-e1dd-af8d-659d8a94815e.png)

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3703986/c5959813-4248-0c50-474e-6d9e881d34fc.png)
Google ChromeでのSharedArrayBufferの有効化

## 目次
[1. はじめに](https://qiita.com/a-kido/private/8192f7606eab7177d505#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
[2. SharedArrayBuffer](https://qiita.com/a-kido/private/8192f7606eab7177d505#sharedarraybuffer)
[3. cross-origin isolation](https://qiita.com/a-kido/private/8192f7606eab7177d505#cross-origin-isolation)
[4.SharedArrayBufferのみを有効にする](https://qiita.com/a-kido/private/8192f7606eab7177d505#sharedarraybuffer%E3%81%AE%E3%81%BF%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B)
<br>

## はじめに
はじめまして、株式会社TechoesのK（入社1年目、エンジニア歴3年目）と申します
今回は、現在担当しているプロジェクトでの機能実装に際してSharedArrayBufferの設定について学んだことを記したいと思います

## SharedArrayBuffer

ZoomVideoSDKで以下の機能を実装するためには、SharedArrayBufferを有効にする必要があるようです。
<br>

- 仮想背景
- １キャンバスに複数のビデオの分割表示（例：配信画面の視聴者ビデオ等）

参考:https://developers.zoom.us/docs/video-sdk/web/sharedarraybuffer/

<br>
ということでGoogleChromeでのSharedArrayBufferを有効にする方法を調べました
<br><br>

## cross-origin isolation
参考サイトによると、Google ChromeでSharedArrayBufferを有効にするためには、ページをcross-origin isolationの状態に設定する必要があるそうです。具体的には、レスポンスヘッダーに以下の設定を追加します。
<br>
```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
参考:https://developer.chrome.com/blog/enabling-shared-array-buffer/?hl=ja
<br>

また、対象ページのmetaタグに以下のように追加することもできます。
```
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp"/>
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin"/>
```
<br>

`cross-origin isolation`が有効になっているかどうかは、ブラウザコンソールで以下のコマンドを実行して確認できます。
```
crossOriginIsolated
```
結果が`true`ならOK
<br>

さらに、SharedArrayBufferが有効かどうかについても、ブラウザコンソールで以下のコマンドで確認できます。
```
typeof SharedArrayBuffer === 'function';
```
結果が`true`ならSharedArrayBufferは有効になっている
<br>

### リソースが表示されなくなる場合
<br>

ただし、上記の設定を行うと、サーバーへのリクエスト時に認証が必要なリソースが表示されなくなる場合があるため、以下の設定が推奨されます。
```
Cross-Origin-Embedder-Policy: credentialless
```

`Cross-Origin-Embedder-Policy: require-corp`を上記の通りに書き換えれば良い

参考:[Cross-origin isolation の条件を緩和する](https://blog.agektmr.com/2021/11/cross-origin-isolation.html#cross-origin-isolation-%E3%81%AE%E6%9D%A1%E4%BB%B6%E3%82%92%E7%B7%A9%E5%92%8C%E3%81%99%E3%82%8B)
<br>

## SharedArrayBufferのみを有効にする
`cross-origin isolation`にせずにSharedArrayBufferのみを有効にしたい場合は、Google Origin Trialsの「SharedArrayBuffers in non-isolated pages on Desktop platforms」にドメインを登録する方法があります。

<br>

リンクはこちら↓
[Google Origin Trials](https://developer.chrome.com/origintrials/#/trials/)
<br>
以下画面の、項目「SharedArrayBuffers in non-isolated pages on Desktop platforms」の「REGISTER」を選択

![スクリーンショット 2024-02-19 11.53.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3714682/129edacd-7c98-1373-ff6e-24d5ea3cb20b.png)
<br>

以下画面にドメインを登録
![スクリーンショット 2024-02-19 12.01.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3714682/e675f598-57d9-8de1-f6a5-5022c67339fd.png)
<br>

ドメイン登録後に発行されたトークンをmetaタグに追加することで、SharedArrayBufferを有効にできます。
```
<meta http-equiv="origin-trial" content="ここにトークンを追加" />
```
<br>

### 注意点
ただし、これは暫定的な対応であり、Googleからcross-origin isolationの緩和策が発表された場合は、それに合わせて対応する必要があります。(期限は2024年6月24日とされていますが、緩和策が出るまでは期限切れにはならないとのこと)

参考:https://developer.chrome.com/blog/enabling-shared-array-buffer/?hl=ja
AIによる爆速開発：画面設計からプログラミングまで自動化！？

近年、AIの進化によりソフトウェア開発のプロセスが大きく変化しています。これまで手作業で行われてきた画面設計やプログラミング作業も、AIの登場により驚異的なスピードに進化しています。今回は、その中でも私が実際に使っている2つのAIツール、UizardとUI Sketcherについて紹介します。
なお、本記事では医師と患者を繋ぐマッチングサービスという架空のサービスを例に話を進めます。

## [Uizard](https://uizard.io/): サービス概要からUIデザインを自動生成
[Uizard](https://uizard.io/)は、画面設計作業を劇的に短縮する革新的なAIツールです。このツールを使えば、サービス概要を入力するだけで、わずか数分で美しく洗練されたUIデザインが生成されます。つまり、開発者は時間をかけてUIデザインを考える必要がなくなり、その時間を他の重要な作業に集中することができます。

### Uizardの使い方
![スクリーンショット 2024-04-01 17.23.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/be56b6f1-750a-fe70-9871-a9614aa33ea6.png)
1. メニューから Generate with Autodesignerをクリックする
2. すると上図のような画面になるので、必要な項目を入力する（日本語でOK）
3. Generate my projectを押して実行する（数分待つ）

![スクリーンショット 2024-04-01 17.31.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/1893b875-1ec0-402c-3742-d746207fda63.png)


実行した結果はこのようになります。
気に入らない画面があれば、削除して画面下部のプロンプトから必要な画面だけを再生成できます。テキストを日本語で生成することができない点は残念なポイントです。（日本語で生成するTipsがあるのでしょうか？）

私は有料プランに加入しました。デザイナーではない私としては、サービスのUIデザインに時間をかけずに、美しいUIデザインが簡単に手に入るのはとてもありがたくお金を払う価値があると思います。

## [UI Sketcher](https://marketplace.visualstudio.com/items?itemName=pAIrprog.ui-sketcher): 画像やイラストからプログラムを生成するVS Codeアドオン
[UI Sketcher](https://marketplace.visualstudio.com/items?itemName=pAIrprog.ui-sketcher)は、画像やイラストを元にしてプログラムを自動生成する、VS Codeアドオンです。このツールを使用すると、UI画像をコードに変換することが可能になります。これにより、デザインと開発の間にあるギャップが縮まり、開発のプロセスが効率化されます。

### UI Sketcherの初期設定
![スクリーンショット 2024-04-01 17.13.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/0aeba1f7-8613-2d1c-cbe4-27eb70c067c8.png)
1. [UI Sketcher](https://marketplace.visualstudio.com/items?itemName=pAIrprog.ui-sketcher)をインストールします
2. VS Codeでコマンドパレットを開いて(Command/Ctrl + Shift + P)、`UI Sketcher: Set OpenAI API Token`を実行します
3. OpenAIのAPIキーを入力します

### UI Sketcherの使い方
![スクリーンショット 2024-04-01 17.10.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/c4e31445-1ac5-d602-3c5b-1ad5f13e7542.png)

1. 出力したいコードの空のファイルを作成して開きます（例えば`Login.jsx`や`index.html`など）
2. VS Codeでコマンドパレットを開いて、`UI Sketcher: Open drawing board`を実行します
3. 開いた画面下部のメニューから画像マークをクリックし、UIの画像をアップロードします
4. make realボタンをクリックして実行します
5. コードが出来上がります

### 意図したコードが出力されなかったら付箋を活用する
![スクリーンショット 2024-04-01 17.04.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/07b8fae6-976a-49df-f139-9b8b583ed9d5.png)

意図したコードが出力されなかったら付箋ボタンからメモを追加して対処できます。例えば、React Nativeのコードを生成して欲しかったのにReactのコードが生成されてしまった場合に、付箋で「React Native App」と指定しておくことで、React Nativeのコードが生成されるようになります。

生成されたコードの質は、プロトタイプに使うには充分だと思います。たまに古い書き方のコードが出力されて動かない時があります。その時は、再生成するか修正してください。
私は同じ画像からコードを３回生成して、それぞれのいいとこ取りをするようにして使っています。

## まとめ
AIによる爆速開発が現実のものとなりつつあります。UizardやUI Sketcherのようなツールの登場により、画面設計からプログラミングまでの作業が劇的に短縮され、開発プロセス全体が大幅に効率化されています。これらのツールを活用することで、開発者はよりクリエイティブな作業に時間を費やすことができ、プロジェクトのスピードと品質を向上させることができます。
サイトパフォーマンス改善方法

## 画像周り

### Webpを使う

主要ブラウザはすべてwebp対応しています。
基本的にすべてWebpにしてください。
時間がない場合は、MVなど容量の大きい画像だけでも実施してください。
下記ページで実施します。

https://saruwakakun.com/tools/png-jpeg-to-webp/

効果：大

### 適切な画像サイズに変更

スマホ画像の横幅を700pxから500pxほどにすると、かなり容量を削れます。
タブレットで見ても大きな違和感はないと思います。
気になるならタブレット用の画像を用意しましょう。

効果：中

## script/CSS周り

### scriptをページの下で読み込み
Gtagなど、指定があるものを除いてすべてページ下部で読み込みましょう。

効果：中

### 不要なWebフォントを読み込まない

※これも基本はページ下部で読み込みましょう。

NG
```
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&family=Noto+Serif+JP:wght@100;200;300;400;500;600;700;900&display=swap" rel="stylesheet">
```

OK
```
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&family=Noto+Serif+JP:wght@400;700&display=swap" rel="stylesheet">
```

通常文字と太字だけなら、400と700だけで問題なし。
それ以外の太さを使いたい場合は、パフォーマンスを押してでも使うべきか、考えること。

効果：大

### minify化

下記ツールなどを使い、スクリプトやCSSファイルを圧縮しましょう。
ただしバージョン管理とは相性が悪いので、1点でもパフォーマンスを上げたいケースだけで良いと思います。

https://rakko.tools/tools/33/

効果：小

## その他

### 無駄な記述の削除

不要な文字やコメントはできるだけ減らしましょう。

効果：極小

### エラーの解消

無駄なスクリプトの読み込みを無くす意味だと効果アリです。

エラー自体は直接的な効果はほぼないですが、エラーは限りなくゼロを目指しましょう。

効果：？
【AWS-CloudFront】静的コンテンツリリース時にCloudFrontキャッシュクリアをしたい

![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/96a9b569-4d96-2ccb-0315-7284b3ccff9a.png)



## 本記事で実現できること
静的コンテンツリリース時にCloudFrontキャッシュクリアを行う

## 前提
- AWSマネコンはログイン済み
- 2024.1.19 時点でのコンソールのUIになります

## 簡単な解説
全部で7フェーズ

①ホーム画面で、画面上部検索欄に[**CloudFront**]と入力。
　出てくるサービスの中から[**CloudFront**]をクリック

![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/15066596-eb88-5731-7400-071e2f8641ef.png)



②[**ディストリビューションID**]をクリックする。


![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/ce401f0a-474f-0f5e-6531-c433d0b48f6a.png)



③[**キャッシュ削除**]のタブをクリックする。

![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/6a7767a1-fbee-d5ed-6272-f50e7ed95a05.png)



④[**キャッシュ削除を作成**]をクリックする。

![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/3ba42b5d-f0d8-0c84-d66a-73550721e558.png)


⑤オブジェクトパスを指定して、[**キャッシュ削除を作成**]をクリックする

![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/a30e8edf-5302-2efb-42d6-e8644d712e70.png)


⑥ステータスを確認する。
　※キャッシュ削除が完了すると、進行中から完了済みに遷移する。

 ![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/ed3f4717-cdbc-8495-61e3-bc84f4707c89.png)


⑦ステータスが完了済みになる事を確認する。


![【AWS-CloudFront】静的コンテンツリリース時に CloudFrontキャッシュクリアをしたい (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3754524/30917e4f-c2ca-6580-0b20-1f7c16689f55.png)


## まとめ
本稿が、静的コンテンツリリース時にCloudFrontキャッシュクリアをしたい方の参考になれば嬉しいです。

ITエンジニア向け、AWS資格取得を目指す方向けに個人ブログで記事も投稿していますので、
合わせて読んでもらえると嬉しいです。
[ことなの学校(個人ブログ)](https://kotona-school.com)

更によりレベル感の高そうなAWS関連の発信はZennの方で行います。
こちらももしよければフォロー、ご確認いただけると嬉しいです。
[Zenn個人アカウント](https://zenn.dev/tomo_itbizblog)

Twitterでも役立つ情報を発信しますのでフォローしてもらえると嬉しいです 
→ [@tomo_ITBizBlog](https://twitter.com/tomo_ITBizBlog)
NotionのタスクをGASでSlackに通知

:::note warn
この記事には具体的なコードがないです．時間があるときに追加する予定です．
:::

## やったこと

- GASでNotionのタスクをSlack経由で毎朝通知できるようにした
- 組み合わせたツールは，「GAS」+「Notion API」+「webhook（Slack App）」
    - GASは，毎日定時にプログラムを実行するためにしｙ
    - Notion APIは，Notionのデータベースのエントリの取得に使用
    - webhookは，webAPI経由でSlackにメッセージを送るために使用

## 感想

- [Zapier](https://zapier.com/)を使うよりも，何が起こっている（起こしている）のか理解できるところがよい
- 他の方々のweb記事を参考にして構築すれば躓くところはなかった
Firebaseイベントを集計した Looker Studioのレポートを 毎日Slackに自動投稿させてみる

弊社のアプリにおいて、日毎の新規ユーザー・アクティブユーザー等のレポートをSlackに自動投稿させたいなーと思い、その仕組みをサクッと作ってみました。

## 大まかな構成
[Google Looker Studio](https://lookerstudio.google.com/)でレポート作成
↓
毎朝メールでレポート送信する設定にしておく
↓
GASで該当メールを見つけて添付ファイルを取得し、Slackに投稿

## 前提
今回の対象アプリでは、FirebaseのAnalyticsを利用してアプリの各種イベントを記録・送信しています。
それを、Firebaseコンソールでの設定でGoogle Analytics(GA4)に連携し、Looker Studio 上でそのデータと連携させることで、SQLなどを書かず、データポータルからイベントを参照・集計してレポートを作成することができます。

## Looker Studio上で簡易なレポートを作成
- メールで送信されるレポートの画像は、データポータルのデザインそのままになります。デフォルトのレポートサイズのままだと、Slack上に表示されるサイズになった際に文字が小さく見づらくなってしまったので、今回は表の文字サイズを大きくして、画像が縮小されても内容が分かる様ようにしました（元のレポートサイズを小さくしておく手もありますが）
- あまり情報が多くても見づらいので、現状、1レポートは1ページのみ=画像1枚のみ にしています

Looker Studio上のレポートはこんな感じ(数字は隠してます)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684189/b9d58a83-e554-a8cf-7d25-7fb2b100caf4.png" width="500">

## レポートメール送信
- Looker Studio上の `共有>配信のスケジュール` で、毎朝8時に自分自身宛にメール送信するように設定

## Slackでのメール受信設定
- Slackに投稿する方法として、Email利用と、SlackのAPIを利用する方法があるようでしたが、APIではToken取得など面倒そうだったので、もう少し簡単に扱えるEmail投稿を利用しています。
- Slackでは、投稿したい該当チャンネルの設定で、メール投稿を有効にしておきます。するとチャンネル専用の投稿用のメールアドレスが発行されるので、そのメールアドレスにメールを送信すると、送信メールの内容がSlackの該当チャンネルに投稿されます。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684189/3144812c-d1d3-f60f-cc09-381f01fca538.png" width="300">

## GAS(Google Apps Script)でメール送信プログラムを書く
- Looker Studioから配信されるレポートメールでは、PDFとJPGの両方が含まれているので、直接そのままSlackにメール投稿させず、一度自分のアドレスで受信し、GASでそのメールを検知、画像入りの本文をSlackにメール投稿するようにしています。

以下、GASの内容です。
```js
function main(){
  //メールから画像取得
  attachment = search_from_email_attachments();
  
  //Slackの投稿先のとなるchで発行した投稿用のメールアドレス
  let address = "XXXXXXXXXXXXX@hogehoge.slack.com"; 
  
  // 自分のアドレスからSlack宛に送るメールのタイトル。分かりやすい名前で適当に。
  let mailTitle = "XXXXのユーザー数レポート";
  
  // Slack上で見やすい様に適当にサイズ調整したimgタグ 
  let mailText = "<img src='cid:inlineImg' width=500>";
  //オプションでhtmlファイルとし、srcタグで利用する画像も指定する
  let options = {
   "htmlBody":mailText,
   "inlineImages":
    {
      inlineImg:attachment
    }
  };
  
  //MailAppで宛先、件名、本文、添付ファイルを引数にしてメールを送付
  res = MailApp.sendEmail(address, mailTitle, mailText, options);
  Logger.log(res);
}

function search_from_email_attachments() {
  // Datastudioからスケジュール配信されたメールを取得（条件に一致する最新のメールを一件取得）
  var Threads = GmailApp.search('from:"looker-studio-noreply@google.com" subject:((レポートのタイトル))', 0, 1);
  var messages = Threads[0].getMessages();
  var message = messages[messages.length - 1];

  // メールから画像を取得
  var attachments = message.getAttachments();
  for(attachment of attachments){
    Logger.log(attachment.getName());
    if (attachment.getName().indexOf('.jpg') > 0) {
      return attachment;
    }
  }
}
```

試しに実行したら、こんな感じでSlackに投稿されました！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684189/e082fdcd-3db9-6bd5-909f-0a755506cd7a.png" width="500">

これを毎朝9-10時の間に実行されるようGASでトリガー設定したので、自動で毎朝Slackに投稿されています:grinning:

## 参考
- https://qiita.com/mida12251141/items/4d0add710de8985ef6b5
- https://auto-worker.com/blog/?p=2827
HomebrewのXZ Utilsバージョンの確認と対処

# HomebrewのXZ Utilsバージョンの確認と対処

2024年3月29日にXZ Utils(以下XZ)にバックドアなどが仕込まれている可能性が発見された。
XZはHomebrewを介して入れている方が多いと思われるので、それに対する確認と適切なバージョンへの更新方法をまとめる。

# 目次
[1.実行環境](#実行環境)
[2.バージョンの確認方法](#バージョンの確認方法)
[3.バージョンの更新](#バージョンの更新)
[4.バージョンの固定](#バージョンの固定)
[5.終わりに](#終わりに)
[6.参考文献](#参考文献)

# 実行環境

    MacOS Ventura 13.5
    Mac book Air 2022 m1
    Homebrew 4.1.18

# バージョンの確認方法
HomebrewでXZをインストールされているか確認するには、以下の方法がある。
- `brew list` インストールされているものをすべて出力
- `brew list --versions xz ` XZのバージョンを簡易的に出力
- `brew search xz` XZに関して検索

Homebrewを介してインストールしたXZのバージョン確認
```bash
brew info xz
```
<details><summary>XZのバージョンを直接確認する場合</summary>

:::note warn
注意
以下のコマンドでの確認は非推奨
:::
~~`xz --version`~~ こちらの方法でも通常であれば確認が可能であるが、対象のバージョンである5.6.0,5.6.1などの5.6系では動作が正しいかの保証がない。なので、このコマンドでの確認は推奨しない。
</details>

これらの方法によりバージョンを確認する。
危険なバージョンである5.6系が存在する場合ダウングレードなどの対処を推奨する。

# バージョンの更新
:::note warn
警告
5.6.0, 5.6.1(5.6系)がインストールされている方は実行を推奨
:::
2024年4月1日現在ではHomebrewがバージョンをロールバックしている。よって、下記のコマンドを実行するだけで5.4.6にロールバックされる。
```bash
brew upgrade xz
```
アップグレード後は、[バージョンの確認](#バージョンの確認方法)を行い5.4.6などの対応のバージョンに変化していることを確認する。

# バージョンの固定
もし自動アップデートが不安な場合は、以下の方法でバージョンの固定が可能。
```bash
brew pin xz
```
このコマンドにより自動アップデートをしてもXZに関しては、アップデートされずに固定される。
また、下記のコマンドでこれを解除が可能。
```bash
brew unpin xz
```
固定されているものの確認は以下の方法でできる。
```bash
brew list --pinned
```

# 終わりに
今回の脆弱性はテストバージョンで確認されたため、私自身も対象のバージョンにアップデートしていなかったので、結果的に大丈夫であった。基本的にバージョンは最新を保つべきだが、このようなこともあるので気をつけたいと思った。

# 参考文献
- Homebrew https://formulae.brew.sh/formula/xz
- ~~XZ Utils https://github.com/tukaani-project/xz~~ 現在非公開
- 記事(随時更新)https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27
【コピペ】特定のHTML要素の下部まで画面をスクロールさせる関数

```js
export function scrollToElementUnderline(elementId: string, duration: number = 375) {
  const element = document.getElementById(elementId);
  if (element) {
    const rect = element.getBoundingClientRect();
    const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
    if (!isVisible) {
      const startPosition = window.scrollY;
      const endPosition = startPosition + rect.bottom - window.innerHeight;
      const startTime = performance.now();

      const animateScroll = (currentTime: number) => {
        const elapsedTime = currentTime - startTime;
        const progress = Math.min(elapsedTime / duration, 1);
        const scrollPosition = startPosition + (endPosition - startPosition) * progress;
        window.scrollTo(0, scrollPosition);
        if (elapsedTime < duration) {
          requestAnimationFrame(animateScroll);
        }
      };
      requestAnimationFrame(animateScroll);
    }
  }
}
```
## 補足
idを指定するstring値を引数に取る
デフォルト引数のdurationはスクロールにかける時間(ms)を指定
AppSync GraphQL APIでIAMユーザー権限による操作がエラーとなる件の対応

## 概要
AppSync GraphQL APIのv2 TransformerでIAMユーザー権限による操作が `Not Authorized to access XXX on type Mutation` のようなエラーで弾かれてしまいます。その対応方法について記載します。

## 原因
AWS ConsoleのAppSyncの「関数」で `[xxxx]auth0Function` の中身を見るとわかるとおり、 `#if( $util.authType() == "IAM Authorization" )` の場合、 `#foreach( $adminRole in $ctx.stash.adminRoles )` の中で `#if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )` の判定が行われています。
権限を持っているLambdaからのアクセスの場合、 `$ctx.stash.adminRoles` にLambdaの名前が入るため条件を通りますが、IAMユーザーによるアクセスの場合、この条件判定で弾かれるためアクセスできません。

## 対応方法
`amplify/backend/api/[apiName]/custom-roles.json` を作成し、中に
```json
{
  "adminRoleNames": ["arn:aws:iam::[AccountId]:user"]
}
```
と書いてデプロイします。
このようにすることで、 `$ctx.stash.adminRoles` に `arn:aws:iam::[AccountId]:user` を含めることができます。実際に設定された `$ctx.stash.adminRoles` はリゾルバのテンプレートを見ることで確認できます。下記のようになっているはずです。
```
$util.qr($ctx.stash.put("adminRoles", ["権限を持っているLambdaの名前","arn:aws:iam::[AccountId]:user"]))
```
これにより `#if( $ctx.identity.userArn.contains($adminRole) ... )` 条件を通り、IAMユーザーでもAppSync GraphQL APIを実行することができるようになります。

## 参考
[Clarity Request: Unexpected "Not Authorized" with IAM and Transformer v2](https://github.com/aws-amplify/amplify-category-api/issues/100)

## バージョン情報
```sh
% amplify -v
12.10.1
```
ODF でのスペース回収操作 (ReclaimSpace)

ODF (OpenShift Data Foundation) を使用しているシステムで、データを削除すると該当PVCの使用量が減少することを確認できる一方で、いつまで待ってもターゲットストレージ使用量の減少は確認できず不審に思っていました。以下のページを参照し、スペース回収操作がデフォルトで有効になっていると理解していたのですが、これが勘違いでした。

**ODFで何も設定しないと、ターゲットストレージのスペース回収操作(Reclaim)は行われないので、注意が必要です。**


https://access.redhat.com/documentation/ja-jp/red_hat_openshift_data_foundation/4.12/html/managing_and_allocating_storage_resources/reclaiming-space-on-target-volumes_rhodf#doc-wrapper

もともとこちらのページに、（現在は記載がなくなりましたが）次のような記載があったため、新規にインストールしたクラスターではスペース回収操作が有効になっていると思い込んでいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164968/5967d89e-a26d-fd51-6512-de89eb391cff.png)

＊ここで言及している`新しくインストールしたクラスターの場合デフォルトで有効になっている`のは、`rook-ceph-operator-config` configmap の設定 `CSI_ENABLE_CSIADDONS=true`の設定のことだったようです。

---

実際には、スペース回収操作を有効にするには、`ReclaimSpace` Jobを実行する必要があり、3通りのやり方がマニュアルに記載されています。今回は、推奨である「Annotating PersistentVolumeClaims を使用したスペース再利用操作の有効化」の実施例を紹介します。

## PVCにアノテーションを付ける
回収操作を有効にしたいPVCごとに設定する必要があります。


PVC `data-0`にアノテーションを付けるコマンド例：
```
# oc annotate pvc data-0 "reclaimspace.csiaddons.openshift.io/schedule=@midnight"
persistentvolumeclaim/data-0 annotated
```
確認コマンド例：
```
# oc get reclaimspacecronjobs.csiaddons.openshift.io
NAME                SCHEDULE    SUSPEND   ACTIVE   LASTSCHEDULE   AGE
data-0-1701951350   @midnight                                     16s
```

一度でも実行されると、`LASTSCHEDULE`に前回いつ実行されたかが表示されます。
```
# oc get reclaimspacecronjobs.csiaddons.openshift.io
NAME                SCHEDULE    SUSPEND   ACTIVE   LASTSCHEDULE   AGE
data-0-1701951350   @midnight                      59s            7d11h
```

＊cronjobと同様、`UTC`でスケジュールされるので注意が必要です。例えば、@midnightと設定すると、JSTの9時にスケジュールされます。

参考：毎日JST 12:15に実行したい場合のコマンド例：
```
# oc annotate pvc data-0 "reclaimspace.csiaddons.openshift.io/schedule=15 3 * * *"
```


## アノテーションの削除
回収操作を無効にしたいPVCに対しては、次のようなコマンドを実行してアノテーションを削除します。

```
# oc annotate pvc data-0 "reclaimspace.csiaddons.openshift.io/schedule-"
persistentvolumeclaim/data-0 annotated
```
PowerShellでエンコードを一括で変更

powarshell shiftjisで開いてutf8へ変更するコマンド

カレントフォルダがrootになり、変更する拡張子を指定する

バックアップはとってね。意図せず変換されることもあるから
バックアップはとってね。意図せず変換されることもあるから
バックアップはとってね。意図せず変換されることもあるから


ファイルを開いてBOMなしのutf8で保存しなおしている。

```
# カレントディレクトリを変換するファイルがあるディレクトリのパスとする
$SourcePath = "."
# 変換するファイルのパターンを.csに設定
$FilePattern = "*.cs"

# BOMなしのUTF8エンコーディングを指定
$utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False

Get-ChildItem -Path $SourcePath -Recurse -Filter $FilePattern | ForEach-Object {
    # Shift-JISでエンコーディングされたファイル内容を読み込む
    $content = Get-Content $_.FullName -Encoding Default
    # 読み込んだ内容をBOMなしのUTF-8で再保存する
    [System.IO.File]::WriteAllLines($_.FullName, $content, $utf8NoBomEncoding)
}

```

これでUnity上の文字化けがなくなる（shift jisだと文字化けする)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2294598/5854794a-826b-33f8-04c6-28e7d924c163.png)
OpenCVを使って研究してみた #1

初回はOpenCVの開発環境構築までの投稿です。
今後、基礎編から応用編、さらには研究編まで紹介しようと思います。
最終的には論文掲載されたOpenCVの応用方法について言及する予定です。
# はじめに
初めまして。[パーソルクロステクノロジー株式会社](https://qiita.com/organizations/persolcrosstechnology)の米津真之介です。
米津玄師とクレヨンしんちゃんを混ぜた感じの名前だと覚えていただければと！！
LinkedInもやっていまして、私の人となりは[コチラ](https://www.linkedin.com/in/2ab53826a/)を参照してください。
仕事では東京大学の[山川研究室](http://www.hfr.iis.u-tokyo.ac.jp/index-j.html)と共同研究をしておりまして、今期からは工場自動化推進も兼務することになりました。
その中で学んだコト、経験したコト、成果を出したコトなどを投稿できたらと思います。
# OpenCVについて
OpenCVは画像処理ライブラリとして有名で、プログラミング言語としてPython、C++、Javaをサポートしています。
近年、Pythonの人気が高いため、最新版のOpenCV 4.**の情報はPythonがほとんどなんですよね。
そのため、OpenCV 4.**のC++でのプログラミングの忘備録を数回に分けて投稿しようと思います。
なんで、C++にこだわるかって？？
それは、高速ビジョンシステムの研究に従事しているため、1msで画像処理をしたく、PythonではなくC++でないとその高速性が実現できないためです。
ちなみに、私の研究では1500 Hzで画像入力⇒画像処理⇒結果出力を繰り返しています。
# PC環境
* OS：Windows11 Home 64ビット
* メモリ：16 GB
* CPU：AMD Ryzen™ 9 5900HS プロセッサー
* GPU：NVIDIA® GeForce® GTX 1650
* 開発環境：Visual Studio
* 言語：C++
# OpenCVの開発環境構築
今回使用するOpenCVのバージョンは最新のOpenCV 4.9.0です。
[公式サイト](https://opencv.org/releases/)から最新版のバージョンのWindows用をクリックすれば自動でダウンロードが完了します。
ダウンロードしたファイル(今回は「opencv-4.9.0-windows.exe」)をダブルクリックしインストーラーを起動します。
ファイル保存場所を指定し、一式データを保存をインストーラーが実施してくれます。
(※注意点は保存場所のパスに英語以外が入らないようにしてください。)
次に、Windowsマークを右クリックし、システム⇒システムの詳細設定⇒背要塞設定タブ⇒環境変数⇒システム環境変数のPathをクリックし、編集します。
新規で行を追加し、先ほどダウンロードしたファイルの「…\opencv\build\bin」を追記し、適用します。
その後、再起動し、開発環境のVisual Studioを起動します。
「新しいプロジェクト」から「コンソールアプリ(C++)」を選択し、ソリューションファイル一式を作成します。
今回は「TESAchan.sln」で作成しています。今後もテストちゃんにしようかな、、、:stuck_out_tongue_closed_eyes:
プロジェクトタブ⇒TESAchanのプロパティを選択し、プロパティページを開きます。
「VC++ ディレクトリ」に以下のパスを追記します。
* インクルードディレクトリ：…\opencv\build\include
* ライブラリディレクトリ：…\opencv\build\x64\vc16\lib
最後に、cppファイルに以下の文章を追記して、OpenCVの準備は完了です。
```
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

#ifdef _DEBUG
#pragma comment(lib, "opencv_world490d.lib")
#else
#pragma comment(lib, "opencv_world490.lib")
#endif

int main()
{
	VideoCapture cap(0);
	if (!cap.isOpened())//カメラデバイスが正常にオープンしたか確認．
	{
		return -1;
	}

	while (1)
	{
		Mat img;
		cap >> img;
		imshow("windowName", img);
		waitKey(1);
  		if (key == 113)
		{
			break;
		}
	}
	destroyAllWindows();
	return 0;
}
```
ちなみに、ライブラリのバージョンNo.のあとの"d"がつくものは「デバック版」で、無印は「リリース版」で使用します。
プロパティ設定で記入してもいいのですが、いつも面倒なのでプログラム中に記述しています。
# おわりに
いかがでしたでしょうか？ここまでは標準的なC++でのOpneCVの環境構築です。
おそらく調べながらで、1時間程度でできるかと思います。
Pythonの場合のOpenCVの開発環境構築はもっと早く、開発環境にAnnacondaを使用すれば5分程度で構築が終わります。
こういった点から、近年Pythonが人気なのでしょうね、、、
それでは、次回「OpenCVを使って研究してみた #2」では実際にどういった画像処理を試したか基礎的なところを紹介していければと思います。

参考にした書籍は唯一のOpenCV 4.**の参考書である「OpenCV4基本プログラミング: さらに進化した画像処理ライブラリの定番」という書籍です。
![91US2dCWRRL._SY466_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3738541/b9452e26-b245-6698-1d5d-cf10978114da.jpeg)

でわでわ:grin:
SVG要素を、SVGファイルとして保存、PNGファイルとして保存

# はじめに
htmlのsvg要素でお絵描きした後、それをファイルとして保存したくなりました。
ダウンロードできれば、パワポとかほかの情報に転載できるので。

# 書き方と説明
答えは下記です。
svg要素の`getElementById`のところと、ファイル名のとこだけ修正すれば、どこでも使えると思います。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="xxePqEw" data-user="yo16" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yo16/pen/xxePqEw">
  Untitled</a> by yoichiro ikeda (<a href="https://codepen.io/yo16">@yo16</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 説明
## 両方共の共通処理
svg要素を、htmlから文字列に変換して、それを元にBlobオブジェクトを作ります。
Blobになると、一時的にURL化できるのでそうしておきます。

## SVGファイル保存
a要素を作って、hrefにBlobのURLをセットしてクリック。

## PNGファイル保存
まず、htmlの`<svg>`にwidthとheightを設定していないと動かないので注意。（大体設定するとは思いますが）

Imageオブジェクトを作って、srcにBlobのURLを設定することでロード。

ロードが終わったら、canvas要素→contextを作ります。
contextに、ロードした画像を描く。

最後にcanvas要素をURL化して、a要素のhrefへセットし、クリック。

ちなみにPNG画像の背景は透過だけど、塗りつぶしたければ、`ctx.drawImage()`の前に`ctx.rect(0, 0, width, height)`とかで四角形を描いちゃえば良い。


# おわりに
よいsvgライフを！:grinning:


# 応援お願いします！
SQLクエリから、カラムレベルのリネージを表示するツールを作っています。
SEやアナリストのSQL初心者の勉強サポートとして、また複雑なクエリの可視化によるミス防止のためになったらいいなと思っています。
※ 今回のSVGから保存する機能はここで使っています。
こんな機能あったらいいんじゃないのー？とか、使ってみたけど遅いよーとか、いろいろ教えていただけると嬉しいです！

https://sql-parse-client-202403.an.r.appspot.com/
AndroidStudioでカレンダーを作ろう

AndroidStudioのインストールが完了していることを前提として始めます．

### 新しいアプリのためのファイルを作ろう

AndroidStudio左上の「ファイル」⇒「新規」⇒「新規プロジェクト」
``「 Alt+F ⇒ N ⇒ Enter 」でも可``

下記のものを選んで「次へ」を押します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/a55fd66b-7865-bc5e-5f37-238f52b65dff.png)


Nameに好きなアプリ名を入力し，
LanguageをJava，
Minimum SDKをAPI 24("Nougat";Android 7.0)を選択します．

XMLファイルとJavaファイルが開かれた状態になれば成功です．

### プロジェクト構造を設定しよう
カレンダーを作成するためにライブラリーを使用しますが，プロジェクトのコンパイルSDKが少なくとも34以上でないといけません．

ファイル⇒プロジェクト構造(Ctrl + Alt + Shift + S)
Compile SDK Versionを34に設定しましょう．
出来たら「OK」．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/1af792d9-f748-8670-124d-3f016d407d6d.png)


### コードを書こう

XMLファイルの中のText Viewを削除し，同様の場所に以下のコードを張り付けましょう．
```activity_main.xml
<CalendarView
        android:id="@+id/calendarView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
```

Javaファイルの一行目以外のを以下のコードに置き換えます．
```MainActivity.java
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.CalendarView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CalendarView calendarView = findViewById(R.id.calendarView);
        calendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
            @Override
            public void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth) {
                // 日付が選択されたときの処理
                // year, month, dayOfMonth を使用して処理を行う
                // 予定のTodoリストとかにしてもいいんじゃない？
            }
        });
    }
}
```

### 完成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/cdf6f21c-80ed-ec4b-7037-12b590677a57.png)

## 参考
https://xprogrammingx.hatenablog.com/entry/2020/09/22/171724
モニターのカラープロパティによってCSSの表示色がバグる

## 今回起こったこと
何故か、HSLで色を指定した時に、指定した色と違う色も表示される...
１日無駄にしたため、戒めとして記事を残そうと思う。

## 症状
Remixでshadcn-uiを使って色々と試していた時に不思議な症状が出た。

1.何故か背景色が2色で表示される色があった。
2.何故かbodyの中身を消すと直る
3.Chromeでは発生して、Safariでは発生しない

## 考察と原因
今回、CSS変数をHSLで初めて使ったため、使い方が悪いのかと思ったが、Chromeでは発生して、Safariでは発生しないため、CSSの設定やプロジェクトのConfigの問題ではなさそうだと推測。

Configやプロジェクトの作り直し、その他色々と試したけれど、明確に差が出る原因を特定できなかった。

一晩明けて、落ち着いてもう一度考え直してみた。
Chromeのみで発生するため、Chromeの設定の可能性が高いと思い調べたところ、Chromeが使うカラープロパティという項目があることを知った。

これにより、Chromeのカラープロパティの設定を変えたところ症状が直った。
Chromeがモニターのカラープロパティをデフォルトで使用しているため、モニター側の問題だと判断できた。

## 対処方法
Macの設定から、モニターのカラープロパティをdisplay P3に変更したところ改善した。
持っているモニターとMacの相性の問題なのか、デフォルトのカラープロパティでは正常に表示されなかった。

## まとめ
そんなこんなで、色々と原因を探していたところ、１日も無駄にしてしまった。
まさか、モニターのカラープロパティが原因で、ページ上の背景が部分的に変わるなんて思わないじゃん...

昨今、ゲーミングモニターなどで部分的に色味が調整されているモニターもあるため、創作活動時はカラープロパティも確認していこうと学びを得た。
kintoneから見積書をメールで送る（CustomineとSendGridの設定）

# はじめに
kintoneでお客様の開発支援をしていると、標準機能では実現出来ない要求（自動入力、帳票出力、メール送信、バッチ処理、バーコード読み取り等々）全てに応えるために、Customineを入れちゃおうってなることがよくありますよね。
今回はCustomineとSendGridで見積書のメール自動送信を実装したのですが、他で詳しく書いてる記事が少なかったので、本記事で共有しておこうと思います。

## この記事の対象者
- kintoneのデータを使ってメールを送りたい。
- CustomineとSendGridの連携方法が分からない。

## 今回実装したメールを送る流れ
1. kintoneで見積情報をレコード登録する。
1. 登録した情報を使って見積書を生成する。
1. ボタン押下でお客様に見積書をメール送信する。　←　★本記事の内容

 ※本記事では3.をCustomineとSendGridで実装しており、その具体的な設定を紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/11c76a61-ae59-7ed1-06af-cf94b95e41f2.png)

# 設定方法
下記を設定する必要があるので、順に説明していきます。
- SendGridの設定
- Customineの設定
## SendGridの設定
### API Keyの取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/c8bf1f71-ed0f-107d-6b36-71b70dc1396d.png)
Settings > API Keys > Create API Keyで、
- API Key Nameを入力（分かりやすければ何でも良いです。今回はMail Sendとしてます）
- Restricted Accessにチェック
- Access DetailsでMail SendをFull Accessに変更

上記設定を行うと、API Keyが表示される。（設定後は再表示できないので控えておく）

### Sender Authentication
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/df8e0c3a-4cfb-f5bc-2c2f-8ab37068a869.png)
Settings > Sender Authentication > Single Sender Verification > Get Startedで、
- From Name（メール受信者に表示される差出人名）
- From Email Address（メール受信者に表示される送信元メールアドレス）
- Reply To（メール受信者が返信した時の返信先メールアドレス）
- Company Addrres / City / Zip Code / Country（会社の所在地）
- Nickname（差出人情報を一意に識別する名前、メール受信者には表示されない）

上記設定を行うと、「From Email Address」に登録したメールアドレスに認証メールが届く。認証を完了させると、VERIFIEDが「赤✖」から「緑✔」に変化する。

:::note warn
本記事ではSingle Sender Verificationで登録していますが、本来はなりすましメール対策のためにDomain Authenticationで設定した方が良いです。
詳しい設定方法はこちらをご参照ください → https://funecyblog.com/manual/150
:::

## Customineの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/be4be2a1-b69a-d6c6-69ab-134585973e38.png)

:::note info
処理の流れは下記に記載しましたが、１つ１つの詳細設定は上の画像でご確認ください🙇（もし設定内容が分からなければ、[Customineドキュメント](https://docs-customine.gusuku.io/ja/)を見てください）
:::

### kintoneアプリのカスタマイズ
- 見積もりアプリ詳細画面でボタン表示
- ボタン押下後に確認ポップアップ
- WebHook起動（疑似的にWebhook「レコード追加」発生）
- メール送信後に完了ポップアップ

### kintoneアプリのWebhook
- 見積書の取得元アプリのAPI接続
- メール文面の取得元アプリのAPI接続
- 見積もりレコード取得
- テンプレレコード取得
- SendGridでメール送信（SendGridの設定で取得したAPI Keysを入力）

:::note info
kintoneの「アプリの設定」で下記設定が必要になります。
- API接続（Customineの処理でkintoneアプリを参照する）
APIトークンの生成で「レコード閲覧」にチェックし、表示されたAPIトークンをCustomineで入力する。
- Webhook（kintoneレコードが追加された時にCustomineの処理を起動させる）
CustomineでWebhook作成した時に表示されるWebhook URLを入力し、通知を送信する条件で「レコードの追加」にチェックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/20f56d72-827b-b5cc-e644-45566b74d395.png)
:::

# 実際に送られたメール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713212/88f7cb20-621a-dac2-0053-86b4acd9c503.png)
こんな感じでkintoneレコード情報を使って見積書を添付したメールが送れました。

# おわりに
見積書作成とメール送信であれば、PrintCreatorとkMailer使えば良いじゃんという考えもありますが、CustomineとSendGridの組合せの方がそもそも値段が安いし、SendGridは大量のメール送信にも対応しており、Customineはkintoneの操作性・見た目とか他にも色々な機能を実装できるので、お得かなと思っております。

今回Qiita初投稿＆kintoneに関する投稿でしたが、最近PowerPlatformを勉強し始めたので、今後はたぶんそっちの記事が多くなると思います。
自分用メモ4

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Iterator;

public class ImageFormatValidator {

    public static void main(String[] args) {
        // 示例图片路径列表，包含可能的路径，有些可能不存在
        List<Path> filePathList = Arrays.asList(
            Path.of("/path/to/image1.jpg"),
            Path.of("/path/to/image2.png"),
            Path.of("/path/not/exist"), // 文件不存在，将被跳过
            Path.of("/path/to/image3.jpeg"),
            Path.of("/path/to/image4.bmp"), // 不允许的格式
            Path.of("/path/to/image5.png")
        );

        System.out.println("Are all images in supported format? " + areAllImagesInSupportedFormat(filePathList));
    }

    public static boolean areAllImagesInSupportedFormat(List<Path> filePathList) {
        for (Path filePath : filePathList) {
            // 仅对存在的文件进行格式检查
            if (Files.exists(filePath) && !isImageFormatSupported(filePath)) {
                // 如果文件存在但格式不被支持，返回false
                return false;
            }
        }
        // 所有存在的文件都符合格式要求或没有文件存在
        return true;
    }

    public static boolean isImageFormatSupported(Path imagePath) {
        try (ImageInputStream iis = ImageIO.createImageInputStream(imagePath.toFile())) {
            Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(iis);

            if (imageReaders.hasNext()) {
                ImageReader reader = imageReaders.next();
                String formatName = reader.getFormatName();

                // 检查格式是否为JPEG, JPG, 或 PNG
                return formatName.equalsIgnoreCase("JPEG") ||
                       formatName.equalsIgnoreCase("JPG") ||
                       formatName.equalsIgnoreCase("PNG");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 文件不存在或无法读取视为跳过检测
        return true;
    }
}