OmniAICreator/Qiita-1.07M huggingface dataset:
ストレージバケットとAzure Storageの設定方法

# はじめに
この記事は、UiPath アドベントカレンダー 2022の25日目の記事ですhttps://qiita.com/advent-calendar/2022/uipath

あまり使っている方がいない（と感じる）Orchestratorの機能であるストレージバケットに関して、記載していきます。

# ストレージバケットとは
Orchestrator（以下OCと記載）のファイルストレージ機能です。
Excelや画像など様々なファイルをアップロード、ダウンロードすることが出来ます。
フォルダ単位で作成することができ、Robotの処理ファイルや変数設定ファイルを配置することなどが可能です。
ストレージバケットに関して詳しくはこちら
https://docs.uipath.com/orchestrator/lang-ja/docs/about-storage-buckets

またストレージバケットはOCサーバーのローカルだけでなく、外部のAmazon S3, MinIO, Azure Storageと連携することが可能です。
外部のストレージサービスを利用するメリットとしては以下があります。
　　・容量の拡張が簡単
　　・OCでのトラブル発生時でも、ファイルの参照が可能

設定方法はローカルが一番簡単ですが、今回はAzure Storageとの連携を行っていきたいと思います。

# 準備するもの
・Azureアカウント
・ストレージアカウント
・OC（本記事ではOC v22.4を使用します）

# 記載している手順
● コンテナの作成
● ストレージアカウントの設定
● アクセスパスワードの取得
● ストレージバケットの設定

# コンテナの作成手順
1. ストレージアカウントに移動し、「コンテナ」を選択
![image2022-7-19_14-21-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/3efb2503-e8fd-6b5a-5d30-5bfb4fd6b208.png)

2. 「コンテナ」を押下し、任意の名前を設定
![image2022-9-13_2-55-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23892. 245/adaeb373-aab4-c438-0fb5-73803c2ecd26.png)

# ストレージアカウントの設定
1. ストレージアカウントの「設定」＞リソースの共有（CORS）」を選択

2. 「Blob Service」にて、以下の設定を行う
　　※許可されたオリジン：OCのURL
　　※許可されたメソッド：GET, PUT
　　※許可されたヘッダー：*
　　※公開されるヘッダー：*
　　※最長有効期間：200
![image2022-9-20_1-48-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/886886a6-6a35-d6d8-0525-c92a5418b8f8.png)


# アクセスパスワードの取得
1. ストレージアカウントの「セキュリティとネットワーク」＞「アクセスキー」に移動

2. 「key1」の「キー」部分を手元にコピー（ストレージバケットの設定で使用します）
![image2022-9-6_0-53-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/545c1ee7-69ff-3a04-67dd-1c3d9eaeb8d3.png)

これでAzure上の操作は完了です。

# ストレージバケットの設定
1. OCにログインし、ストレージバケットに移動]

2. 「新しいストレージバケットを追加」を押し、Azure Storageにて以下設定を行う
　※名前：任意のものを指入力
　※Azure Storageコンテナー名：手順2-3で指定した名前を入力
　※Azure Storageエンドポイントサフィックス：core.windows.net
　※Azure Storageアカウント名：手順1-4で指定した名前を入力
　※Azure Storageアクセスパスワード：手順4-2で入手したものを入力
![image2022-9-6_0-55-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/a4523ac8-f42b-a4ae-e771-232a48b559ca.png)


# Studioからのファイルアップロード手順
1. Studioより「ストレージファイルをアップロード」アクティビティを使用し、以下の設定を行います。
　※コピー先のスプレッドシート：アップロードするファイルのストレージバケット上での保存場所、名前を指定します。
　※ストレージバケット名：保存先のストレージバケット名を指定します。
　※フォルダーパス：保存先のストレージバケットが存在するOC上のフォルダを指定します。
　※パス：アップロード対象のファイルパスを指定します。
![image2022-9-20_2-4-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/e62e1026-3730-ff12-42a2-dd8107d27719.png)

2. 実行し、指定のストレージバケットにファイルが存在することを確認します。
![image2022-9-20_2-2-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2389245/8b1ac03c-5982-d1e1-5e62-f8836a97fd22.png)

# 最後に
次回はストレージバケットとAzure filesとの連携方法をご紹介できるようにします～
CoarseGrainedLock & ImplicitLock

![21.CoarseGrainedLock & ImplicitLock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11525/563390cb-128f-da8b-fb95-22ad1372bb68.png)

https://bliki-ja.github.io/pofeaa/CoarseGrainedLock/

https://bliki-ja.github.io/pofeaa/ImplicitLock/

Coarse Grained Lock は操作に含まれる個々のデータの単位ではなく、データ群をまとめてひとつの意味を成す単位で排他ロックを確保するパターンです。

Optimistic Offline Lock (楽観的ロック) には、更新操作の衝突を避けるための仕組みが必要です。ひとつの操作単位が複数のメカニズムにまたがると、一部が正常で一部が矛盾するような、判断の難しい状況を生みます。Git のブランチマージは変更行の衝突をとてもうまく検出してくれますが、同じ行を変更していなかったとしても、同じファイルを変更していると、マージ後にプログラムコードのロジックが矛盾する場合がありますね。複数のブランチ作業で、同じファイルやディレクトリに変更が発生しているときは要注意です。念のため rebase し、直列的なコード編集と考えるのが無難です。同様に、データのサブ構造の異なる部分を変更した操作だから排他的に扱わなくていいと思っていると、それぞれは正しい意図で行った操作が、全体としては矛盾する結果を生むことがあります。

Pessimistic Offline Lock (悲観的ロック) においても、ひとつの操作の中にいくつものロックポイントがあると、他の操作とのデッドロックを起こしやすくなります。デッドロックとは、A のロックを獲得してから B のロック解除を待つ処理と、B のロックを獲得してから A のロック解除を待つ処理が、お互いににらみ合ったまま動かなくなるような状況です。デッドロックのための待ち時間とエラーリトライ処理はサーバーリリースの無駄です。ロックのキーをなるべくシンプルにするのは、パフォーマンスに貢献する可能性もあります。

Implicit Lock (暗黙的ロック) は、ビジネスロジックを実装するプログラマーがロックを意識しなくてもよくなるよう、アーキテクチャのメカニズムを担うレイヤーに排他処理を設けるパターンです。単にプログラミングが楽になるだけがそのメリットではありません。確保と解除の対応を確実にしたり、意図せず無駄に重複したロックを確保してしまったりする事故を防ぐことが、Implicit Lock の大事な約目です。

トランザクション範囲の決定を、横断的関心として切り分け、プロクシパターンやアノテーションを使った AOP にするのも Implicit Lock になります。サービスレイヤーを汎化して共通部分でロックを扱うのもそうです。一連のデータ更新をフレームワークが自動的にトランザクション化している場合もあります。とくに Unit Of Work パターンは、最終的な変更差分の反映に暗黙的な排他制御を持つべき箇所の代表です。
React +  react-router-dom v6 でURLを設定する ( App.js を編集するだけの最小構成 )

# ポイント

BrowserRouter > Routes > Route の入れ子構造にする

# インストール

`npm install react-router-dom@6`

# App.js

App.js を以下の内容にする

```react
import { Routes, Route, BrowserRouter } from 'react-router-dom';

function Hello() {
  return <h2>Hello</h2>;
}

function Home() {
  return <h2>Home</h2>;
}

function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router v6</h1>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/hello" element={<Hello />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
```

# ブラウザアクセス

`/` と `hello` で表示が変わるのが分かる

![image](https://user-images.githubusercontent.com/13635059/209929661-b4090b3c-c0be-4e28-b283-be5011cc857c.png)
![image](https://user-images.githubusercontent.com/13635059/209929656-a2d8cd6f-e217-4ceb-807c-92699a6dee06.png)

# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# Twitter

https://twitter.com/YumaInaura
[OutSystems]ScreenのAggregateとData Actionは、何を通信しているのか

Reactive Web Appで、Screenに作成するAggregateやData Actionは実行時にHTTPSで通信する。実際にはどんな通信を行っているかを確認してみた。

動機はAggregateで、例えばBuilt-In Functionを使ってFilterしているときに、通信をユーザーが操作することで不当なデータ取得をできないか、という懸念。

# 環境
Personal Environment(Version 11.17.0 (Build 36291))
Service Studio(Version 11.53.31)

# ScreenのAggregate
## テスト用Aggregateと画面変数の設定
以下のScreenと、それにぶら下がるAggregateを作成し、通信を確認してみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/92cd6d39-8d31-fa76-027d-d930fa63f596.png)


1. Input Parameter "In1"はAggregateで使用していない
2. Local Variable "TableSort" (Sortingで使用）、"StartIndex"/"MaxRecord" (Propertyで使用）はAggregateで使用している
3. Local Variable "Dummy"はAggregateで使用していない
4. Filteringでログインユーザーで絞り込むためにGetUserId() Built-In Functionを使用している

## Method
POST

## URL
https://\<host\>/\<モジュール名\>/screenservices/\<モジュール名\>/\<UI Flow名\>/\<Screen名\>/ScreenDataSet\<Aggregate名\>

## Request Header
特に気になるデータはなかった

## Request Body
以下の構造のJSONを渡している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/5c18a994-cd0b-c90e-d74a-a4d43fb5801f.png)

- inputParameters: 空のオブジェクト。AggregateにはInput Parameterが無いので、何に使うか不明
- screenData: 以下の形式のオブジェクト
    - variables: ScreenのInput Parameter/Local Variable「全て」を属性として含む。つまり↑の例の場合、In1/TableSort/StartIndex/MaxRecords/Dummy全てを含む
- versionInfo: これは恐らく、サーバー側のモジュールの更新を判別するためのモジュールのバージョン情報。背後で使われる情報なので無視して良いと思う
- viewName: \<UI Flow名\>.\<Screen名\>

## 結果の検討
### 変数が全て渡ってしまう
使わないものも含めて変数が全てリクエストに載っているので、場合によっては注意が必要。例えば、クライアント側にのみ保持し、通信に出す機会は最小限にしたいセキュアな情報は扱いに注意が必要。

### Built-In Functionはサーバー内（というかAggregate内）に閉じて通信には口が空いていない
ので、ユーザーが通信を操作して想定外のFiltering条件を指定されてしまうような心配はなさそう（当然だが）。

ちなみに、Built-In Functionのドキュメントでは、Aggregateで使用した場合について、

> Database: Function is evaluated before the aggregate is executed.

とあるので、サーバー側でAggregate実行の直前に評価されるということのようだ。

# Data Action
## テスト用Data Actionと画面変数の設定
画面は↑のAggregateのテストに使ったのと同じものなので、Data Actionの定義のみ載せる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/af42b42f-5a84-4b6a-902b-936bf4cf0cef.png)

## Method
POST

## URL
https://\<host\>/\<モジュール名\>/screenservices/\<モジュール名\>/\<UI Flow名\>/\<Screen名\>/DataAction名\<Data Action名\>

## Request Header
特に気になるデータはなかった

## Request Body
基本的にAggregateと同じ。
相違点として、inputParametersは項目からなかった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/742e0a73-5179-846b-e354-360b73f033f0.png)

## 結果の検討
### 基本的にはAggregateと同じだが
↑で書いたようにRequest BodyのinputParameters項目がなかった。Data ActionにはInput Parameterがないのだが、それはAggregateでも同じなので、むしろAggregateのBodyにはある理由がわからない。
2022年に買ったもの

数年ぶりに「今年買ったもの」を書こうと思います。直接技術には関係ないかも知れないけど微妙に関係しているものについて4点についてです。

## iPhone SE 3

昔は出先でもiPhoneでコード触ったりもしてたけど、在宅勤務でコード触るのはほぼ自宅のみとなりました。
iPhoneはジョギング中に英語ヒアリングの練習を兼ねてPodcastを聴くのに使ったりなどしています。チーム内に英語話者がいたり、顧客に米国企業がいたりするので、英語がもっと楽に聴けるようになりたいです。

## Titan Slim

[Titan Slim](https://www.unihertz.com/en-jp/products/titan-slim?variant=42897655890159) は物理QWERTYキーボード付きのAndroidスマートフォンです。TIC-80向けのコードを書いて動かしてみたりとか、Rubyスクリプトを書いたりもしてみたけど、例によって在宅勤務によって実のところわざわざスマホでコードを書く意味は大してないので、試してみたという程度です。
片手で英単語の入力が簡単にできるのは案外便利で、印刷物などの知らない英単語を英和辞典や英英辞典で引いたりもします。

## Keyboardio Model 100

[Keyboardio Model 100](https://shop.keyboard.io/products/model-100) は左右分離のキーボードです。現在、コーディングや作文などで使うメインのキーボードとなっています。
USB接続です。左右の接続に使うケーブルはEthernetと同じものが使えます。
本体の材質はウォルナットとメイプルから選べます。自分はウォルナットを選びましたが、多分どちらを選んでも木の暖かさを感じられると思います。キースイッチは4種類から選べますが、多分何となれば自分で入れ替えできるんでないかと思います。
キー配列などの変更は [Chrysalis](https://github.com/keyboardio/Chrysalis) というアプリから行えます。

## COFO Chair Pro

[COFO Chair](https://cofochair.jp) はデスクチェアです。10年以上使っていた安物の椅子が壊れてきたので、買ってみました。仕事中も仕事じゃない時も自室にいるときにはほぼこれに座っています。
PremiumとProのどちらを選ぶか迷ったのですが、自分にはProで良かったです。値段の差が4万円ある一方で、自分にとって機能の差はアームレストの調整範囲ぐらいに感じられたのでProの方でいいかなと。まだ使いはじめて1ヶ月ほどなので耐久性について語る時期ではありませんが、今のところ故障もなく使えています。
若干他の椅子よりも大きいのかも知れませんが、自分には合っています。アームレストの高さが丁度よく、ここに肘を乗せると上記のKeyboardio Model 100での運指がとても楽に感じます。腰のサポートも仕事をする姿勢を保つのに役立っていると感じます。
リクライニングとヘッドレストとフットレストは以前使っていた椅子にはありませんでしたが、ちょっと一休みするには良いですね。コードのビルド中にちょっとiPadなどで調べものしたりするときにリクライニングがあるとリラックスできます。また、「10分だけ昼寝」というときにはフットレストとヘッドレストが役立ちます。
Nuxt.jsでGoogle広告からランディングした際にオーガニックで計測されてしまう

初めての投稿になりますので、読みにくい部分もあるかと思いますがご容赦ください。
Nuxt.jsにてサービス運用しており、Google広告からのコンバージョンが正しく計測できない問題があり、解決に時間を要したので情報共有のために投稿します。

## 前提
* Nuxt.js v2.14.0
* Nuxt.js 用の Google タグ マネージャー モジュール
@nuxtjs/gtm v2.4.0
https://github.com/nuxt-community/gtm-module
* GTMにて「Google アナリティクス ユニバーサル アナリティクス」を設置

## どういう事象が起こっていたか？
1. Google広告から会員登録LPページにランディング
GAのリアルタイムレポートで確認して、この時点ではgoogle/cpcで計測されている
1. 項目を入力
1. 登録ボタンを押下して、完了ページへ移動
この時点でgoogle/cpcのセッションが失われて、**新たにgoogle/organicのセッションが発生。**
CVはgoogle/organicで発生したと計測される。

## 解決方法
参考ページをもとに解決済みです。

### 1. GTMにてカスタムHTMLタグを用いて、以下のJSを設置
```html
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  originalLocation: document.location.protocol + '//' +
                    document.location.hostname +
                    document.location.pathname +
                    document.location.search
});
</script>
```

### 2. ユーザ定義変数にて、1.で設定したoriginalLocationをGTMで使用できるように、変数を設定
![スクリーンショット 2022-12-29 17.32.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663560/eea2de6e-26a4-e79c-9d39-d372a50eeacf.png)

### 3. カスタム JavaScript 変数を作成する
```js
function() {
  return {{Data Layer Variable - originalLocation}} || window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.search;
}
```
※```{{Data Layer Variable - originalLocation}}```は2.で作成した変数です。

![スクリーンショット 2022-12-29 17.34.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663560/f30910ef-47fd-b9d1-b47b-7a0267dfdc2b.png)

### 4. GAユニバーサルに3.で作成した変数をアタッチする
![スクリーンショット 2022-12-29 17.36.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663560/7d66cf74-3401-f8be-a412-3e4f427627ae.png)

### 5. 上記を公開し、CVがorganicで計測されないことを確認できました！


## 参考ページ
https://github.com/nuxt-community/gtm-module/issues/96

https://www.blackdroneconsulting.com/how-to-fix-spa-gtm-referrer

https://www.simoahava.com/gtm-tips/fix-rogue-referral-problem-single-page-sites/
cpp copy folder and file, create folder if not exist

```cpp
int CreateFolderIfNotExist(std::string folder) {
    if (!fs::is_directory(folder) || !fs::exists(folder)) { // Check if src folder exists
        std::ostringstream oss;
        oss << "mkdir " + folder; // prevent space in source path
        std::string command = oss.str();
        std::cout << command << std::endl;
        system(command.c_str());
    }
    return 0;
}

int CopyFileAndFolderCommand(std::string file, std::string destanation) {
    CreateFolderIfNotExist(destanation);
    std::cout << destanation << std::endl;
    std::replace(file.begin(), file.end(), '/', '\\');
    std::ostringstream oss;
    if (fs::is_directory(file)) {
        oss << "xcopy " <<"\""<< file << "\"" << " " << destanation <<  "\\" << "*" << " /Y"; // prevent space in source path
    } else {
        oss << "xcopy " <<"\""<< file  << "\"" << " " << destanation << " /Y"; // prevent space in source path
    } // is file
    std::string command = oss.str();
    std::cout << command << std::endl;
    system(command.c_str());
    return 0;
}
```
Unityお勉強　2Dアクションゲームの作り方

作り方を教えるという内容ではありません。
どうやって作るのか？を調べるよいう話です。

公式の2D Gamekitをみて作り方をお勉強しようと思います。
ちょっとずつ内容を理解していくと思うので徐々に更新予定です。

■お勉強内容としては一先ず。
・キャラクタ移動
・ダメージ床
・背景
・移動床

実際にサンプルプロジェクトを見つつ、
youtubeの解説動画を見つつ自分の理解を深めて行こうというのが目的です。
あと、後から作る方法をすぐに調べられるようになればいいなと思います。
電子工作初心者がM5Stack Core2の環境構築をしてみる（M1 Mac）

# 対象読者
M5Stackシリーズの環境構築をPlatformIO（VSCodeの拡張機能）でやりたい方
（M1 Mac以外でも参考になると思います。）

# 自分の環境
>・macOS Monterey　バージョン12.3
・MacBook Air (M1, 2020)
・チップ: Apple M1

# 背景
:::note
ArduinoIDEはビルドと書き込みが遅い。
:::
上記を踏まえ、VSCodeの拡張機能として使うことができるPlatformIOを用いて環境構築することにした。
（ちなみに後の大学の授業では、Arduinoをインストールするように言われた。）

# 環境構築
1. VSCodeにPlatformIOの拡張機能を入れる。（VSCodeの再起動が必要）
2. PlatformIOで、新規プロジェクトを作成する。
![スクリーンショット 2022-12-29 17.08.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1790537/6bc3d426-6557-7988-9452-521b4d064992.png)
    PIO Home > New Projectで新規プロジェクトを作成
    必要事項は
    >①　Name: test（任意）
    ②　Board: M5Stack Core2（別のマイコンなら変える）
    ③　Framework: Arduino
    
    でOK!!
    とりあえずHello Worldをやってみる。
    以下は、src > main.cppに記述。
    ~~~C++:main.cpp
    #include <M5Core2.h>
    
    /* After M5Core2 is started or reset
    the program in the setUp () function will be run, and this part will only be run once.*/
    void setup()
    {
      M5.begin();                  // Init M5Core2.
      M5.Lcd.print("Hello World"); // Print text on the screen (string)
    }
    
    /* After the program in setup() runs, it runs the program in loop()
    The loop() function is an infinite loop in which the program runs repeatedly */
    void loop() {}
    ~~~

3. PIO Home > Libraries から M5Core2 をインストール
**M5Core2 by M5Stack** と表記されているもの
他のマイコンの場合はここを変えれば良さそう。
4. USB Driverのインストール（ここで詰まった）
:::note alert
USB Driverのインストールは不要と書いてある記事もあったが、自分の環境ではUSB Driverをインストールしないと書き込みがうまくいかなかった。
:::
ダウンロードは https://github.com/WCHSoftGroup/ch34xser_macos のREADMEに従えば良い。
・それなりに新しいMacならdmg形式のドライバーをインストールする。
・中国のサイトだが怖がる必要はない（と思う）![スクリーンショット 2022-12-29 17.11.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1790537/3137e093-fdb3-92d7-c441-4595dde729e2.png)
# ビルドと書き込み
ビルドしてエラーがないか確認。大丈夫であれば、アップロード。アップロードが成功すると、自動でHello WorldとM5stackの画面に出力される。
![スクリーンショット 2022-12-29 16.52.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1790537/5b17eb8a-f4cc-379b-cac1-801424066e90.png)

:::note
左のチェックマークから順に、『ビルド』『アップロード（書込み）』『クリーン』になっている。
コンセントマークは『シリアルモニター』。クリックするとターミナルに接続できるCOM一覧が表示されて、番号を指定すると接続できるっぽい。
:::
![IMG_1720.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1790537/c906dcf8-1bb5-8cac-e431-839bb947270c.jpeg)
Hello Worldはいつになっても嬉しいです。

# メモ
・多分Macのバージョンが違っても同じ方法で行けると思う。
・M5stackの電源の切り方がわからなかった→電源ボタンが側面左側についていて長押しする。
・USB Type-Cを挿すときにスムーズにさせなくて壊れないか不安になった。
・書き込み時にエラーが発生すると、M5Stack Core2はフリーズするっぽい。
・書き込み時に自動でCOMポートが選択されるらしい。
・別途C++の環境構築をしたりする必要はない。

# 参考
1. https://playing-engineer.com/wordpress/2022/06/08/m1-macmonterey%E3%81%A7m5stack%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%82%92/
2. https://netmark.jp/2021/04/2021-04-26-20-18.html
c++ normalize filepath

```cpp
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1;
#include <experimental/filesystem> // or #include <filesystem> for C++17 and up
    
namespace fs = std::experimental::filesystem;

std::string NormalizePath(const std::string& messyPath) {
    std::filesystem::path path(messyPath);
    std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(path);
    std::string npath = canonicalPath.make_preferred().string();
    return npath;
}

path = NormalizePath(messyPath)
```
2022年の振り返りと2023年の目標

<strong>目次</strong>
* フロントエンド
* バックエンド
* インフラ
* IT全般
* その他
* 来年の目標
* まとめ

# フロントエンド
* Udemy「【2022年最新】【JavaScript＆CSS】ガチで学びたい人のためのWEB開発実践入門（フロントエンド編）」2周
* ドットインストールでHTML、CSS、JavaScript講座
*  本「1冊ですべて身につくHTML & CSSとWebデザイン入門講座」80%読了

# バックエンド
* 独学エンジニアpart1〜part3
* Udemy「【PHP, MYSQL, Apache】ガチで学びたい人のためのWEB開発の基礎(バックエンド編)」60%実施
* Udemy「はじめてのSQL ・データ分析入門 -データベースのデータをビジネスパーソンが現場で活用するためのSQL初心者向コース」55%実施
* Udemy「はじめてのRuby on Rails入門-RubyとRailsを基礎から学びウェブアプリケーションをネットに公開しよう」100％実施
* Railsチュートリアル第8章まで実施(全14章)
* 本「プロを目指す人のためのRuby入門」34%読了

# インフラ
* Udemy「AWS：ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得」
* Udemy「Git： もう怖くないGit！チーム開発で必要なGitを完全マスター」100%実施
* 本「Amazon Web Services 基礎からのネットワーク&Webサーバー構築」100%読了
* 本「新しいLinuxの教科書」100%読了
* 本「1週間でLPICの基礎が学べる本」100%読了
* 本「スラスラわかるネットワーク&TCP/IPのきほん」100%読了

# IT全般
* Udemy「令和5年版:現役講師が教える【基本情報技術者試験　科目A】講座　基礎理論から経営戦略までの全範囲を網羅」50%実施
* 本「リーダブルコード」100%読了
* 本「Web系エンジニアになろう」100%読了
* 本「Webを支える技術」100%読了
* 本「プロになるためのWeb技術入門」100%読了

# その他
* Udemy「【初心者から上級者まで】１日で学べるエクセルの教科書　マスターコース」70%実施
* Udemy「The Complete ServiceNow System Administrator Course」100%実施
* 本「コンサル一年目が学ぶこと」100%読了
* 本「ずるい仕事術」100%読了
* 本「7つの習慣」100%読了
* 本「限りある時間の使い方」100%読了
* 本「The Psychology of Money」100%読了
* 本「テクノロジーが予測する未来」100%読了
* 本「数値化の鬼」100%読了
* 本「エッセンシャル思考」100%読了
* 本「転職の思考法」100%読了
* 本「ジャイソン流のお金の増やし方」100%読了 

# 来年の目標
<strong>サーバーサイドエンジニアとしてWeb系企業の受託 or 自社開発企業へ転職</strong>

そのために実施すること
* 3月末までに独学エンジニア完了
* 4月末までにポートフォリオ作成
* 6月末までにWeb系受託 or 自社開発企業へ転職

<strong>サーバーサイドエンジニアとして1人前のレベルになる</strong>

そのために実施すること
* エンジニアの人脈を広げる(情報交換できる人最低2人)
* プライベートで最低週15時間は学習時間を確保する

<strong>その他</strong>
* プライベートでGo言語の学習をする
* 技術書を最低10冊は読む
# まとめ
こうしてリストアップしてみると思った以上にたくさんのことができた1年だった。ただ、知識不足だったり、エンジニア界隈の色んな事情を知らなかったことで、軸がブレブレだったかもしれない。来年はサーバーサイドエンジニアという目標に向けてなるべく寄り道せずに突き進みたい。(もちろん広い視野を持つことは大切だが、、)
最後に、1年間自分お疲れ様。来年は飛躍の年にします！
【Qt学習メモ】DAY 1: 環境構築

# はじめに
ちょうど年末年始で趣味に費やす時間もあるので、ここでQtについて学んでおくことにした。

# DAY 1（2022-12-29）: 環境構築
今回は以下の環境でQtの学習を行う。

- KDE Neon User Edition
    - Ubuntuベース、かつQt採用のKDEなので
    - KDE Neon自体の開発や調査が目的ではないため、User Editionを選択
    - 執筆時点のバージョンは5.26
- Qt 5.15.2
- Qt Creator
    - 執筆時点では9.0.1

## Qt Accountを作成
Qtのインストール画面の冒頭で、Qt Accountのユーザー名とパスワードの入力が求められる。
このため、あらかじめQt Accountを作成しておく。

https://login.qt.io/register

## VirtualBoxにKDE Neonをインストール
KDE Neonの公式サイトより、インストールメディアをダウンロード。

https://neon.kde.org/download

- VirtualBoxで新しい仮想マシンを作成
    - rootfs用のVHD（64GB）に加え、Qtインストール先のVHD（300GB）を用意
    - ネットワークアダプターは`ブリッジアダプター`に変更
        - NAT+ホストオンリーの構成だと、wgetやQt Creatorのインストーラーがインターネットに出られないようだった
    - クリップボードの共有を双方向にしておく
- VirtualBoxにKDE Neonをインストール
- 初期設定
    - 更新: `pkcon refresh && pkcon update`
    - VirtualBox Additionsのインストール
    - aptでインストール
        - openssh-server
        - gccとg++: Qtのビルドに必要
    - `スクリーンの節電`の無効化
    - `画面を自動的にロックする`（デフォルト5分）の無効化
    - Qt用VHDのセットアップ
        - パーティション作成
        - フォーマット（ext4）
        - fstabを編集して自動マウントするように設定

再起動して以下構成になればOK

```bash
$ lsblk -o +FSTYPE
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS     FSTYPE
sda      8:0    0   64G  0 disk                 
├─sda1   8:1    0 55.2G  0 part /               ext4
└─sda2   8:2    0  8.8G  0 part [SWAP]          swap
sdb      8:16   0  300G  0 disk                 
└─sdb1   8:17   0  300G  0 part /home/user/work ext4
sr0     11:0    1 1024M  0 rom
```

## QtおよびQt Creatorのインストール

```bash
$ cd ~/work
$ wget https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run
$ chmod u+x ./qt-unified-linux-x64-online.run
$ ./qt-unified-linux-x64-online.run
```

`インストール フォルダー`の画面で、インストール先とコンポーネントの選択が行える。
インストール先を`~/work/Qt`とし、`カスタムインストール`にチェックがついた状態で次へ進む。

![VirtualBox_Qt Creator_29_12_2022_13_47_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292557/640df6b9-2760-c647-1f8c-96bc4701eefc.png)

そうすると、Qt 6に加えてQt 5も選択できる（Qt 5.15.xって5.15.7まで出てた気がするが、なぜか5.15.2）。

![VirtualBox_Qt Creator_29_12_2022_13_48_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292557/b4efdb96-200e-48c9-745f-b983a4079145.png)

ほかは特に変更せずそのままインストールした。

![VirtualBox_Qt Creator_29_12_2022_13_32_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292557/4274765e-159c-0c0d-ac8c-79136ba4edcc.png)

## Qt Creatorの設定
`編集`→`Preferences`から設定ダイアログを開く。

`キット`→`キット`の`Desktop Qt 5.15.2 GCC 64bit (規定)`にエラーや警告が出ないことを確認する。

![Screenshot_20221229_143818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292557/7f7d879d-5924-cec3-1f5b-935cc37bb019.png)

同様に、`キット`→`Qt バージョン`の`Qt 5.15.2 GCC 64bit`にもエラーや警告が出ないことを確認する。

![Screenshot_20221229_143826.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292557/74604177-557e-14a5-f657-a64f5972c94c.png)

明日以降、Qt Creatorを使ってコーディングを行っていく。
任意のコントロールでダブルバッファリングを有効化する

## ダブルバッファリングとは
↓詳しく知りたい人向け
> コントロールが描画される時、通常は、背景が描画されてから、前景が描画されます。この描画を直接画面に行うと、背景が描画された時に一瞬前景が消えるため、ちらついて見えてしまいます。
>ダブルバッファリングを有効にすると、コントロールの描画はまずメモリバッファー（オフスクリーンバッファー）に行われます。そしてすべて描画し終えたところで、メモリバッファーの画像が画面にコピーされます。このようにダブルバッファリングでは画面に描画されるのは1度だけなので、ちらつきを抑えることができます。
>ただしダブルバッファリングはそれだけメモリを消費しますので、環境や使い方などによってはパフォーマンスを低下させる可能性もあります。


*https://dobon.net/vb/dotnet/control/doublebuffered.html から引用させて頂きました。*

簡単に説明すると、ダブルバッファリングを有効化しないとコントロールの描画などを行ったときにちらつきが発生する可能性があるから、ダブルバッファリングを有効化しようってことです。
※メモリやCPUの消費が増える可能性もある

## ダブルバッファリングを有効化するための関数

``` C#
using System;
using System.Reflection;
using System.Windows.Forms;
/// 中略

/// <summary>
/// 任意のコントロールのダブルバッファリングを有効化・無効化します。
/// </summary>
/// <param name="control">任意のコントロール</param>
/// <param name="IsDoubleBuffered">有効 or 無効</param>
void ControlDoubleBuffered(Control control, bool IsDoubleBuffered)
{
    control.GetType().InvokeMember(
    "DoubleBuffered"
    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
            null,
            control,
            new object[] { IsDoubleBuffered });
}
```
## 使い方
例:
``` C#
/// 中略
ControlDoubleBuffered(this, true);
ControlDoubleBuffered(pictureBox1, true);
```

## 最後に
この関数を使うと、どのコントロールでも、簡単にダブルバッファリングが有効かできて便利だと思います。
ListViewとか、PictureBoxに描画するときとかに結構ちらつくので、ダブルバッファリングはなるべく使った方がいいと思います。
ansible-playbookでpostfixをインストール・設定する(SMTPサービスを利用する)

## はじめに
Postfixをインストールして設定の反映までを実施するansible-playbookです
ansible-playbookの簡単な使い方はわかる前提で記載しています
gmailなどのSMTPサービスを使用する前提です

## 環境の前提
- Ubuntu 20.04
- 導入するサーバは、内側から外側へインターネット接続できること
- メール送信にはポート587を使用するためFW設定などは適切に設定すること

## ファイル構成
```
L postfix
    - main.cf
    - sender_canonical.db
    - sasl_passwd.db
- postfix_server.yml
- hosts
```

## main.cfの作成
main.cfを事前に作成しておきます
- mynetworks
    Postfixを導入するサーバを利用してメール送信をおこなうネットワークセグメントを指定してください
- relayhost
    利用するSMTPサービスのアドレスを指定してください
- smtp_sasl_password_maps
    SMTPサービスの登録をするときに設定したパスワードです。postmapによりハッシュ化したものを指定します
- sender_canonical_maps
    送信元のメールアドレスを指定します。こちらもハッシュ化したものを指定します
```
inet_protocols=ipv4
mynetworks=127.0.0.0/8, xxx.yyy.zzz.0/24
relayhost = smtp.xxx.xxx.com:587
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
sender_canonical_maps = hash:/etc/postfix/sender_canonical
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 2
```

## hosts
IPアドレスは導入するサーバの値を指定します
```
[postfix_server]
aaa.bbb.ccc.ddd
```

## postfix_server.yml
やっていることは、以下の5つです
- Postfixをインストールする
- main.cfのコピー（事前に作成したファイルに置き換える）
- sasl_passwd.dbをコピー
- sender_canonical.dbをコピー
- postfixサービスを再起動してmain.cfの設定を反映させる
```yaml
- name: setup postfix server
  hosts: postfix_server
  tasks:
    - name: configure postfix using debconf
      become: true
      debconf:
        name: postfix
        question: "{{ item.question }}"
        value: "{{ item.value }}"
        vtype: "{{ item.vtype }}"
      with_items:
        - { question: postfix/main_mailer_type, value: "No configuration", vtype: select }
    - name: install postfix
      become: true
      apt:
        name: postfix
        update_cache: yes
    - name: copy main.cf
      become: true
      copy:
        src: ./postfix/main.cf
        dest: /etc/postfix
    - name: copy sasl_passwd.db
      become: true
      copy:
        src: ./postfix/sasl_passwd.db
        dest: /etc/postfix
    - name: copy sender_canonical.db
      become: true
      copy:
        src: ./postfix/sender_canonical.db
        dest: /etc/postfix
    - name: postfix service restart
      become: true
      systemd:
        name: postfix
        state: restarted
        enabled: yes
```

## おわりに
説明不足だと思いますので不明な点があればコメントいただければわかる範囲で追記します
ActiveRecord::ProtectedEnvironmentError: の対処方法

## はじめに

Railsチュートリアル第7版の10章の終わりにデータをRenderにデプロイしたところ、タイトルのようなエラーが発生しました。

## 環境

OS: macOS Monterey Version 12.3
Ruby: 3.1.2
Rails: 7.0.4

## 発生したエラー

Manual DeployのDeploy latest commitを実行後、Renderのログに以下のエラーが発生しました。

```
Dec 29 11:35:59 AM  rails aborted!
Dec 29 11:35:59 AM  ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'production' database.
Dec 29 11:35:59 AM  If you are sure you want to continue, run the same command with the environment variable:
Dec 29 11:35:59 AM  DISABLE_DATABASE_ENVIRONMENT_CHECK=1
Dec 29 11:35:59 AM
Dec 29 11:35:59 AM  Tasks: TOP => db:migrate:reset => db:drop => db:check_protected_environments
Dec 29 11:35:59 AM  (See full trace by running task with --trace)
Dec 29 11:35:59 AM  ==> Build failed 😞
```

## 原因

Rails5より実装された、本番環境のDBを誤って削除しないようにするための防止機能が働いているようです。

## 対処方法

エラーログにあるように、render-build.shに```DISABLE_DATABASE_ENVIRONMENT_CHECK=1```を追加しました。

```:render-build.sh
#!/usr/bin/env bash
# exit on error
set -o errexit
bundle install
bundle exec rails assets:precompile
bundle exec rails assets:clean
DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset
bundle exec rails db:seed
```

## 参考サイト

- [Renderによるデプロイ](https://qiita.com/Naaa0/items/8e2ad76e40e3a638ddfc)
- [Rails 5に入ったDB破壊系taskの防止処理について](https://www.tumblr.com/y-yagi/142064773890/rails-5%E3%81%AB%E5%85%A5%E3%81%A3%E3%81%9Fdb%E7%A0%B4%E5%A3%8A%E7%B3%BBtask%E3%81%AE%E9%98%B2%E6%AD%A2%E5%87%A6%E7%90%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
laravelでwithの中のwhereでRequestを使う方法

# 環境
- php:8.1
- laravel:9.23.0

# 対象
- laravelでwithを使っていてその中でRequestの値を条件として使いたい人

# 前提
- usersテーブルはcompaniesテーブルとリレーションされている状態

# 方法
- ダメなパターン
これだとRequestが使えない状態になっています
```php:UserController.php
<?php

declare(strict_types=1);

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function getSearchUsers(Request $request)
    {
        $query = User::query()->with(['company'])->whereHas('company', function ($query) {
            $company_id = $request->query('company_id');            
            $query->where('companies.company_id', '=', $company_id);
        });
        return $query->get();
    }
}

```
- OKなパターン
変更点は **function ($query) use ($request) {**　の所です
```php:UserController.php
<?php

declare(strict_types=1);

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function getSearchUsers(Request $request)
    {
        $query = User::query()->with(['company'])->whereHas('company', function ($query) use ($request) {
            $company_id = $request->query('company_id');            
            $query->where('companies.company_id', '=', $company_id);
        });
        return $query->get();
    }
}

```
# まとめ
useを使用することで親スコープの変数を使うことができる
Ruby 3.2.0をインストールする時、"The Ruby openssl extension was not compiled."エラーが出てインストールできない場合の対策

## 環境
- MacBook Pro M1 Max
- macOS 13.1 (Ventura)
- rbenv 1.2.0-50-g593f820

## 問題
Ruby 3.2.０をインストールしている途中、以下のエラーが出ました。
```zsh
Inspect or clean up the working tree at /var/folders/49/nz02t3_136xgn4jlw6z9r1q40000gn/T/ruby-build.20221229163135.84880.33u6aF
Results logged to /var/folders/49/nz02t3_136xgn4jlw6z9r1q40000gn/T/ruby-build.20221229163135.84880.log

Last 10 log lines:
ERROR: Ruby install aborted due to missing extensions
Configure options used:
  --prefix=/Users/yuto/.anyenv/envs/rbenv/versions/3.2.0
  --enable-shared
  --with-readline-dir=/opt/homebrew/opt/readline
  --with-gmp-dir=/opt/homebrew/opt/gmp
  --with-openssl-dir=/usr/local/opt/openssl@1.1
  CC=clang
  LDFLAGS=-L/Users/yuto/.anyenv/envs/rbenv/versions/3.2.0/lib -L/opt/homebrew/opt/imagemagick@6/lib
  CPPFLAGS=-I/Users/yuto/.anyenv/envs/rbenv/versions/3.2.0/include -I/opt/homebrew/opt/imagemagick@6/include
```

ログを確認したら以下の原因が分かりました。
```zsh 
The Ruby openssl extension was not compiled.
```

おそらく`openssl`関連の問題が起きていると思われます。

## 解決策

`openssl`, `readline`, `libyaml`を再インストールします。
```zsh
brew uninstall openssl@1.1 readline libyaml
```

それから以下のコマンドで無事にRuby 3.2.0がインスールできました。
```zsh
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)"
rbenv install 3.2.0
```
ansible-playbookでpostfixをインストール・設定する

## はじめに
Postfixをインストールして設定の反映までを実施するansible-playbookです
ansible-playbookの簡単な使い方はわかる前提で記載しています

## 環境の前提
- Ubuntu 20.04
- 導入するサーバは、内側から外側へインターネット接続できること
- メール送信にはポート25を使用するためFW設定などは適切に設定すること

## ファイル構成
```
L postfix
    - main.cf
- postfix_server.yml
- hosts
```

## main.cfの作成
main.cfを事前に作成しておきます
mynetworksの値は、Postfixを導入するサーバを利用してメール送信をおこなうネットワークセグメントを指定してください
```
inet_protocols=ipv4
mynetworks=127.0.0.0/8, xxx.yyy.zzz.0/24
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 2
```

## hosts
IPアドレスは導入するサーバの値を指定します
```
[postfix_server]
aaa.bbb.ccc.ddd
```

## postfix_server.yml
やっていることは、以下の3つです
- Postfixをインストールする
- main.cfのコピー（事前に作成したファイルに置き換える）
- postfixサービスを再起動してmain.cfの設定を反映させる
```yaml
- name: setup postfix server
  hosts: postfix_server
  tasks:
    - name: configure postfix using debconf
      become: true
      debconf:
        name: postfix
        question: "{{ item.question }}"
        value: "{{ item.value }}"
        vtype: "{{ item.vtype }}"
      with_items:
        - { question: postfix/main_mailer_type, value: "No configuration", vtype: select }
    - name: install postfix
      become: true
      apt:
        name: postfix
        update_cache: yes
    - name: copy main.cf
      become: true
      copy:
        src: ./postfix/main.cf
        dest: /etc/postfix
    - name: postfix service restart
      become: true
      systemd:
        name: postfix
        state: restarted
        enabled: yes
```

## おわりに
gmailのようなSMTPサービスを使用する場合はプラスアルファが必要です
似たような内容になりますが別記事にします
Javaで何か作ってみる(2022/12/20 Day61)

## 今日やった事
- Springの動画教材を見た。（**オブジェクト指向を復習しておきましょう** まで見た。）
- Macに、VSCode + Spring Boot環境の構築を始めた。
## 参考にしたもの
- [Spring 入門 ～ SpringBoot 2 を使ってお問い合わせアプリとToDoアプリを作る ～](https://www.udemy.com/course/java_spring_beginner/)   
- [【簡単】VSCode＋Spring Bootアプリケーション開発手順 | こへいブログ](https://kohei.life/spring-vscode/)
## 所感
- VSCodeで拡張機能までのインストールまでは出来た。
- Spring Bootの使い方については、本当これから。

## 明日の予定
- 引き続き、動画教材を進めていく。
- 併行して、VSCode + Spring Boot環境の構築を進めていく。

## ※2022年12月29日追記
- 以下の理由により、今回で、Java、Springの勉強を一旦終了する。  
    - 市場のトレンドを考慮
    - モチベーション  
    (C#を長くやっていて、Javaを勉強していて、新たな知見がほとんどない) 
- 年明けから、別の言語の勉強を進めていく予定。
サブディレクトリでhuskyを使用する

## TL;DR

- huskyの実行ファイルはGitの`core.hooksPath`で設定される
- Gitにはそもそもhooks機能があり、huskyはその実行ファイルを可視化しているだけ

## 前置き

`husky`と呼ばれる、Gitのあらゆるアクションに付随してコマンドを実行してくれる便利な存在があります。代表的なものはpre-commitでlint等を動かしてくれるものでしょうか。lintはついつい実行し忘れてしまうので、commit前に自動で実行してくれると便利ですよね。

huskyはnpmモジュールなので、基本的にはnodeを使用するディレクトリでinstallすればよしなに使えるようになるのですが、`package.json`と`.git`を別階層に存在させたい、そんなときもあると思います。例えば、下記のようなディレクトリ構成を持つときです。

```bash
$ tree
.
├── back
├── front
│   └── package.json
└── .git

```
このようなときに、`front`ディレクトリで`husky`をインストールすると以下のようになるわけです。

```bash
$ tree
.
├── back
├── front
│   ├── package.json
|   ├── .husky
|   │   ├── _
|   │   │   ├── .gitignore
|   │   │   └── husky.sh
|   │   └── pre-commit
|   ├── package-lock.json
|   └── package.json
└── .git
```
frontでhuskyをインストールするときにgitが無いよとか怒られる可能性があるのですが、この場合はgitのあるディレクトリで

```bash
$ git config --local --add core.hooksPath front/.husky
```
と設定してあげると動きます。なぜでしょうか？

そもそも、GitにはGit Hooks機能が存在しています。設定値は

```bash
$ git config --local --get core.hooksPath
```
で確認できます。何もしていなければ設定されていないと思います。（適当なgitディレクトリで試してみてください。）
平易に理解すると、huskyはその設定ディレクトリを変えてあげているだけです。
つまり、huskyをインストールするのすら面倒なときは`.git/hooks`に適当なファイルを生成してあげ、`core.hooksPath`に`.git/hooks`を設定すると動きます。

```bash
$ git config --local --add core.hooksPath .git/hooks
$ echo "echo 'hoge'" > .git/hooks/pre-commit
$ git commit --allow-empty --allow-empty-message -m ""
hoge
```
となるわけです。(もし動かない場合は、`chmod +x .git/hooks/pre-commit`を実行してみてください。)
らびやんさんの語る LovyanGFX

LovyanGFX アドベントカレンダー 2022 締めの記事は動画です。

# らびやんさん出演の質疑応答

「低レベル勉強会2022年12月」
において、後半の LovyanGFX ハンズオンを行いました。
締めに行われたらびやんさんとの質疑応答を Youtube で公開しました。

https://youtu.be/j_bFehEkBu0?t=0
- らびやんさんが LovyanGFX を開発するようになった経緯
- M5StickC+ で動くサーモカメラの画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/c4e016fe-bd31-6084-6e96-8f2e8735306e.png)
https://youtu.be/j_bFehEkBu0?t=295
-Webブラウザで画面描画したものと同じものが見える
https://youtu.be/j_bFehEkBu0?t=340
- UI のこだわり
- スプライトの性能がすごい!
- だけどスプライトという言葉を使ったのはすごい失敗だったかな
https://youtu.be/j_bFehEkBu0?t=790
- M5GFX と LovyanGFX の関係と相違点
https://youtu.be/j_bFehEkBu0?t=1320
- MLX90640 搭載の M5StickC/M5StickC+ サーマルカメラ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/b2474e57-480b-2e21-387f-f66f1a39c99b.png)
https://youtu.be/j_bFehEkBu0?t=1142
- フォントのお話
https://youtu.be/j_bFehEkBu0?t=1142
- フォントが内蔵しているが使い方を誤ると悩ましい
- LovyanGFX for PC ではソースコードがデカすぎて MSVC でビルドできない
https://youtu.be/j_bFehEkBu0?t=1540
- 日本語が簡単に使えるのは感激涙もの
- 対応の MCU プラットフォームはできれば増やしたい
https://youtu.be/j_bFehEkBu0?t=1820
- HALライブラリを導入しようと考えている
https://youtu.be/j_bFehEkBu0?t=1880
- F-ZERO並の描画をするデモンストレーション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/92f5f86a-b894-d464-5b7c-c8d7ab9cf46d.png)
https://youtu.be/j_bFehEkBu0?t=2003
- オープンハードテラスでも深い話が

# アドベントカレンダー振り返り

LEDパネルキット AKBONE2022 が らびやんさんのご助力で LovyanGFX が使えるようになり、勉強と啓蒙のために LovyanGFX を使うハンズオンを 12月に行いました。
そのためのテキストづくりと、ハンズオンの宣伝などを兼ねてこのアドベントカレンダーを企画し、12月29日になりましたが完走しました。

しかしながら LovyanGFX for PC と フォントのお話で けっこう埋まってしまい、まだまだ取り上げなければならないことはたくさんあるのですが、この続きは来年に・・・

ではまた LovyanGFX アドベントカレンダー 2023 でお会いしましょう!
Spring Boot 依存関係について

## はじめに
今回は、Spring BootでのWEBアプリケーション開発で、最低限の必要になる依存関係について書いていきます。

## 依存関係とは
Spring Bootで開発を進める上で、様々な機能を提供してくれるものです。
今回はGradleを使っていきます。

## 最低限必要になる依存関係
こちらの機能があれば、簡単なWEBアプリケーションが作れます。
ざっくり説明していくとこんな感じです。

|依存関係名|　機能説明　|
|:--:|:---:|
|Spring Boot DevTools|開発用の機能を提供するライブラリ。アプリケーション起動中にコードを編集した場合に自動的に反映するホットリロードを使用するために導入する。|
|Lombok|Getter/Setterなど機械的に作成するメソッドをアノテーションのみで自動生成するライブラリ。単純作業になるコードを削減するために導入する。|
|Spring Web|WEBアプリケーションを開発するために必要な機能を提供する。WebAPI開発には必須。|
|Spring Data JDBC|RDBMSに接続するために必要な機能を提供する。|
|MyBatis Framework|RDBMSへの操作を楽にするためのORM(Object Relational Mapping)機能を提供する。|
|H2 Database|JVM上で動作するRDBMSを提供する。開発効率が良くなるため、9章でMySQLを導入するまでの間利用する。|
|Spring Security|ログイン機能やサインイン機能などを実装するためには必須|
|Validation|WEBアプリケージョンでよく使われるバリデーションを実装するために必要|
|thymeleaf|Spring Bootのアプリケーションでは必須になるテンプレートエンジン|
|spring boot starter test|Spring Bootでのテスト機能を実装する際に必要になる。|

少々抜けているものもあるかと思いますが、以上の依存関係はSpring BootのWEBアプリケーション開発では必須になるものです。

```
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security:2.7.1'
	implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.7.1'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.0'
	implementation 'org.springframework.cloud:spring-cloud-starter'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

## レイアウト作成に役立つ依存関係
こちらは、BootStrap、FontAwesome、jqueryの依存関係です。

```
implementation 'org.webjars:bootstrap:4.3.1'
implementation group: 'org.webjars', name: 'jquery', version: '3.3.1'
implementation 'org.webjars:font-awesome:5.13.0'
```

以上になります。
Google mapをJetpack Composeで扱うための環境構築

公式ドキュメントのインストール手順通りに進めてもうまく行かない箇所があったのでここに調査結果をまとめる。
なお、すでにGoogle mapのAPIキーは取得しているものとする。

# プロジェクトを作成する
AndroidStudioのNew ProjectからEmpty Compose Activityを選択し、プロジェクトを作成する。

# Google mapをAndroidで扱えるように設定する
Google maps platformの[Set up an Android Studio project](https://developers.google.com/maps/documentation/android-sdk/config)を参考に、以下の設定をしていく。

### moduleレベルのbuild.gradleに依存関係を追加
```gradle
dependencies {
    implementation 'com.google.android.gms:play-services-maps:18.1.0'
    // ...
} 
```
バージョンは以下から最新バージョンを確認して指定する
https://mvnrepository.com/artifact/com.google.android.gms/play-services-maps?repo=google

### APIキーを保持する
APIキーをプロパティファイルから取得するライブラリを追加するため、以下をprojectレベルのbuild.gradleに追記する。
```gradle
plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false
}
```
次にmoduleレベルのbuild.gradleに以下を追記する
```gradle
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
```
local.propertiesにAPIキーを追記する。
AndroidManifest.xmlに直書きするのではなくlocal.propertiesにAPIキーを記述することで、git管理からAPIキーを除外することができる。
```properties
MAPS_API_KEY=取得したAPIキー
```
AndroidManifest.xmlにmeta-dataを追記する
```xml
// ...
<application
        //...
        tools:targetApi="31">
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />
        <activity
    
```

### Google Play ServiceバージョンをAndroidManifest.xmlに追記する
```xml
<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />
```

### 位置情報のpermissionを追加する
```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

```

# Jetpack ComposeでGoogle mapを扱えるようにする
インストール手順は[Maps Composeライブラリの公式](https://developers.google.com/maps/documentation/android-sdk/maps-compose)に記載があるが、依存関係を追加するだけでは以下のようにアプリ実行時にエラーが発生する。
```gradle
dependencies {
  //...
  implementation 'com.google.maps.android:maps-compose:2.8.0'
}
```
エラー内容
```
Module was compiled with an incompatible version of Kotlin. 
The binary version of its metadata is 1.7.1, expected version is 1.5.1.
```
調べたところmaps-composeの2.5.3より上記エラーが発生するようになっていた。

### 対処法
結論から言うと、kotlinのバージョンを上げる必要があった。
kotlinのバージョンとComposeのバージョンは対応させる必要があるため、
公式の[互換性対応表](https://developer.android.com/jetpack/androidx/releases/compose-kotlin?hl=ja)を参考に、build.gradleの記述を修正した。
```gradle
buildscript {
    ext {
-        compose_version = '1.1.0-beta01'
+        compose_version = '1.3.1'
    }
 }
plugins {
-    id 'org.jetbrains.kotlin.android' version '1.5.31' apply false
+    id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}

```
さらに1.3.1版のComposeライブラリはandroidのcompileSdkに33を要求するので、moduleレベルのbuild.gradleの以下を修正した。
```gradle
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
```

# まとめ
上記で一通りComposeでGoogle mapを扱うための環境は整った。
BigQueryの配列に対する集合演算っぽいもの

BigQueryの配列操作の関数化のメモ書きです。
関数化の実用性はあまりないと思いますが、配列を使った演算や条件をクエリに書くときの参考に。

## 2つの配列から重複を除いて結合（||演算バージョン）
```sql
CREATE TEMP FUNCTION ARRAY_UNION(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT ARRAY_AGG(DISTINCT i ORDER BY i) FROM UNNEST(a1 || a2) AS i)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_UNION(a,b) -> [1,2,3,4,5,6,7]
```

## 2つの配列の共通部分の配列（INTERSECTバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_INTERSECT(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT ARRAY_AGG(DISTINCT i ORDER BY i) FROM (
    SELECT i FROM UNNEST(a1) AS i INTERSECT DISTINCT
    SELECT i FROM UNNEST(a2) AS i ) w)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_INTERSECT(a,b) -> [3,4,5]
```

## 2つの配列の共通部分の配列（JOINバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_INTERSECT(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT ARRAY_AGG(DISTINCT i ORDER BY i) FROM
    UNNEST(a1) AS i JOIN UNNEST(a2) AS j ON i = j)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_INTERSECT(a,b) -> [3,4,5]
```

## 2つの配列の差(a1-a2)の配列（EXCEPTバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_EXCEPT(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT ARRAY_AGG(DISTINCT i ORDER BY i) FROM (
    SELECT i FROM UNNEST(a1) AS i EXCEPT DISTINCT
    SELECT i FROM UNNEST(a2) AS i ) w)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_EXCEPT(a,b) -> [1,2]
```

## 2つの配列の差(a1-a2)の配列（JOINバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_EXCEPT(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT ARRAY_AGG(DISTINCT i ORDER BY i) FROM
    UNNEST(a1) AS i LEFT JOIN UNNEST(a2) AS j ON i = j
    WHERE j IS NULL)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_EXCEPT(a,b) -> [1,2]
```

## 包含関係：2つ目の配列が1つ目の配列の部分集合(a2⊂a1)か判定（COUNTバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_SUBSET(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT CASE WHEN COUNT(*) = 0 THEN TRUE ELSE FALSE END
    FROM UNNEST(a2) AS i
    WHERE i NOT IN (SELECT i FROM UNNEST(a1) AS i ))
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_SUBSET(a,b) -> FALSE

a = [1,2,3,4,5]
b = [1,3,5]
ARRAY_SUBSET(a,b) -> TRUE
```

## 包含関係：2つ目の配列が1つ目の配列の部分集合(a2⊂a1)か判定（EXISTSバージョン）
```sql
CREATE TEMP FUNCTION ARRAY_SUBSET(a1 ANY TYPE, a2 ANY TYPE)
AS (
  (SELECT CASE WHEN
    EXISTS (SELECT 1 FROM UNNEST(a2) AS i
    WHERE i NOT IN (SELECT i FROM UNNEST(a1) AS i ))
    THEN FALSE ELSE TRUE END)
);

a = [1,2,3,4,5]
b = [3,4,5,6,7]
ARRAY_SUBSET(a,b) -> FALSE

a = [1,2,3,4,5]
b = [1,3,5]
ARRAY_SUBSET(a,b) -> TRUE
```
コードレビューがチームの生産性を低下させているかもしれない

## はじめに

こんにちは。with で Android エンジニアをしている石田です。
突然ですが普段の仕事の中でコードレビューはどのタイミングで行っていますか？

**ついつい後回しにしてしまうことも多いのではないでしょうか。しかしこれがチームの生産性を低下させてしまっているかもしれません。**

通常コードレビューの話題が上がるときってコードレビューのやり方について議論することが多いと思うのですが、本記事では **「コードレビューのスループットが下がると実は結構生産性下がってるよね」** という話と **「どのようにコードレビューのスループットを向上させるか」** という話をしたいと思います。以下、コードレビューを単にレビューと呼びます。また with の Android チームを with-android と呼びます。

## コードがマージされるまでの過程

本題に入る前に書いたコードがマージされるまでの過程を整理してみます。
**3. のレビュー依頼** と **4. のレビュー返却** のやりとりは通常何度か繰り返し発生します。ちなみに with-android の場合は平均すると 2 往復くらいのやりとりをマージまでに行っています。


これまでコードレビューをやるタイミングはメンバーの裁量に任されていました。レビューを依頼した同日中にレビューを返してくれることもあれば、依頼から数日たった後に催促されてようやく取り掛かるということもありました。

それでは 3. と 4. のレビューのやりとりが遅れることによりどのような問題が発生し得るのか考えてみます。


![コードがマージされるまでの過程](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/6ceb8cd7-9a07-aece-9260-2d6ddccfea1c.png)



## レビューが遅れることによるオーバーヘッド

レビューが遅くなることによるオーバーヘッドは想像以上に大きいです。 with-android で感じていた課題感を3つ挙げます。

#### 1. マージコンフリクトが発生しやすくなる

実装開始からマージするまでの時間が長くなると、当然ながらマージコンフリクトが発生しやすくなります。**レビューが完了していざマージしようと思ったらコンフリクトしててマージできない😇** といった経験は誰しもあるのではないでしょうか。レビューを早く済ませることで無駄なマージコンフリクトの修正作業をある程度減らすことができます。

#### 2. Pull Request の内容を忘れてしまう

エビングハウスの忘却曲線で示されているように人間の記憶はかなりのペースで失われていきます。レビューが遅れるとレビュアーどころかコードを書いたオーナーの記憶すら怪しくなっていきます。レビューを早く済ませることでプルリクの内容を思い出すのに余計な体力を使わずに済みます。

#### 3. レビューのレスポンスが遅いと、双方のレスポンスが遅くなる悪循環に陥る

人間はレスポンスが遅いものに対して関心が減っていきます。サイトの表示速度が数秒遅いだけで直帰率が大幅に上がる話とおそらく同じ話で、レビューに対するレスポンスが遅いと連鎖的にレビューのやりとりが遅くなるという現象が with-android では発生していました。レビュー依頼やレビュー返却をお互いに早く済ませて~~互いに圧をかけることで~~レビューのペースを維持することができます。


## レビューを早くする方法

レビューが遅くなることによるオーバーヘッドは大きいぞという話をしたので、ここからはレビューを早くするために with-android が実施した 3 つの方法(+ おまけ)を紹介します。

#### 1. 「レビューはできるだけ早くやるべき」という共通認識をチームで持つ

「コードレビューを後回しにすることは結局チームの生産性を下げることになるから、これからはコードレビューにはできるだけ早く対応しましょう」という話をチームでした上で**コードレビュー依頼は Slack のメンションと同じ** という合言葉を繰り返し使って浸透させました。

「できるだけ早く」と言うとその言葉の意味が人によって違いますし、形骸化しそうだったので Slack のメンションと同じくらい早くレスポンスしましょうという風に、チームメンバーが具体的にイメージしやすい例えを使うのがよいと思います。

加えて 1 日に 1 回、後回しにならないようにできれば午前中にレビューを全部消化する習慣をつけるといいですよという話もしました。

#### 2. GitHub の Scheduled reminders を設定する

GitHub に [Scheduled reminders](https://docs.github.com/en/organizations/organizing-members-into-teams/managing-scheduled-reminders-for-your-team) という機能があり、レビュー待ち状態の Pull Requests を Slack に通知することができます。 **with-android ではこの通知をリマインダーではなく*アラート*と呼んでいます。** 通知が飛んでくる=レビューに遅れが発生していることを意味しているからです。通知する条件はチームに合わせて設定するとよいと思いますが、with-android の場合は **少なくともプルリクを出した翌日にはレビューが返ってきている状態を意識して平日に 18 時間以上レスポンスがない場合に通知する** 設定にしています。

#### 3. Pull Request のサイズを小さくする

これは研究でもたびたび指摘されていることなのですが[^1]、Pull Request のサイズが大きいとレビューが遅くなりますし、バグを発見する頻度も下がると言われています。なので Pull Request のサイズは小さく保つのがよいです。そのために with-android では実装に入る前にタスク分割を行い計画を立てることを推奨しています。

#### おまけ: レビューを優秀なエンジニアに集中させない

これは努力目標といった感じなのですが、レビューを優秀なエンジニアに集中させない方がいいよねという話があります。with-android のように 4~5 人くらいの規模のチームだと一番優秀な人がマネージメントなりの開発業務以外の役割を兼務しているものです。なのでチームメンバーの中で最も忙しく、すぐにレビューを返せるとは限りません。従って可能な限りレビューを属人化させず、全チームメンバーに適切に分散してボトルネックを作らないようにできればよいと思います。

## まとめ

コードレビューのスループットが下がると生産性が下がるという話とその改善方法を紹介しました。コードレビューはついつい後回しにされがちなタスクだと思いますが、もしチームで課題感を感じているならば一度議題に挙げてみてはいかがでしょうか。


[^1]: [例えばこちらの論文](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/rigby2013convergent.pdf)
問題と解答をハイパーリンクで紐付ける方法

# コード
```
\documentclass[uplatex,dvipdfmx,12pt]{article}

\usepackage{hyperref}
\usepackage{hyperref}
\hypersetup{
  colorlinks,citecolor=blue,filecolor=blue,linkcolor=blue,urlcolor=blue
  }

\newcounter{mycount}

\newcommand{\qn}[1]{
  \subsubsection*{
  \label{sec:qn\thesection\alph{mycount}}
  \hyperref[sec:an\thesection\alph{mycount}]{\thesection\alph{mycount}\quad\textit{#1}}
  \stepcounter{mycount}}
  }

\newcommand{\an}{
  \subsubsection*{
  \label{sec:an\thesection\alph{mycount}}
  \hyperref[sec:qn\thesection\alph{mycount}]{\thesection\alph{mycount}}
  \stepcounter{mycount}}
  }



\begin{document}
\tableofcontents

\section{Test} \setcounter{mycount}{1}
  \qn{Test problem}
  Something.

  \qn{}
  Something.

  

\addcontentsline{toc}{subsection}{Answers and Hints}
\subsection*{Answers and Hints} \setcounter{mycount}{1}
  \an
  The answer of the first question.

  \an
  The answer of the second question.

\end{document}
```

# 出力例
![qiita_probset-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436198/611704a2-1979-dde1-cf85-c369a2cdcb9d.png)

- 「1a Test problem」を押すと答えの「1a」に、「1a」を押すと「1a Test problem」に飛ぶようになっています。
lambda Amazon API Gateway APIキー認証を用いた認証付きAPIを作ってみる

# 概要

- lambdaとAPI Gatewayを使ってAPIキー認証を使った認証付きAPIを作ってみたので方法をまとめる。

# 情報

- 筆者はMacOSの端末を使っている。

# 方法

## lambdaの準備

1. 下記の情報でlambda関数を作成する。
    - 関数の種別：一から作成
    - 関数名：returnHello
    - ランタイム：Node.js 18.x
    - アーキテクチャ：x86_64
    - 基本的な Lambda アクセス権限で新しいロールを作成
1. 関数作成時に作成されたindex.mjsの内容は特に変更せず、Testを実行して、200のステータスと「Hello from Lambda!」の文字列が返されることを確認する。

## API Gatewayの準備

1. lambdaの「関数の概要」から「トリガーを追加」をクリックする。

    ![returnHello_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/351bdc55-f7c5-82c4-8aad-fb8923e66689.png)

1. 「ソースを選択」から「API Gateway」を選択する。

    ![トリガーの追加_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/9f0f443a-40aa-7d13-5b34-e1bacbe05973.png)

1. 下記の様に設定する。
    - intent: Create a new API
    - API type: REST API
    - Security: API key
1. 内容が問題無さそうなら「追加」をクリックする。

    ![トリガーの追加_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/88ae138c-5bbf-f7df-f472-1b485dcb07b5.png)

1. lambdaの設定 → トリガーからAPI Gatewayがトリガーとして設定されていることを確認する。

    ![returnHello_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/cf301151-a170-83f8-02ad-c0ac73e3f5bb.png)

## curlを用いて実行

1. まずはAPI keyを渡さない状態でAPI endpointにcurlでリクエストを送ってみる。
1. 下記のコマンドをターミナルで実行する。

    ```bash
    curl エンドポイントURL
    ```

1. 下記の様に権限がない旨のレスポンスが帰ってくる。

    ```
    {"message":"Forbidden"}
    ```

1. 次にcurlのヘッダーにAPI keyを添付して実行してみようと思う。
1. ブラウザからAmazon API Gatewayを開く。
1. APIの一覧から今回の作業で追加されたAPIの「returhHello-API」をクリックする。

    ![API_Gateway_-_APIs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/d84040b2-642b-7c7c-e9e8-0258c2e33b63.png)

1. 左サイドバーの「APIキー」をクリックする。

    ![API_Gateway.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/9c1c0c73-64a2-e76a-e023-720fbe3624eb.png)

1. 「returnHello-Key」をクリックする。

    ![API_Gateway.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/34c625e6-2bd4-706a-614b-bd8c22e14625.png)

1. APIキーの「表示」をクリックしてAPIキーを表示する。（ここで表示されるランダムな文字列は第三者が閲覧できる場所への記載はしないほうが良いです。）

    ![API_Gateway.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/166a6c4a-f2d1-0960-d857-463494819f0a.png)

1. APIキーをコピーする。
1. 下記のコマンドをターミナルで実行する。

    ```bash
    curl -H 'x-api-key:コピーしたAPIキー' エンドポイントURL
    ```

1. 下記の様にindex.mjsで定義した内容が返された。

    ```
    "Hello from Lambda!"
    ```