OmniAICreator/Qiita-1.07M huggingface dataset:
[Android] scrcpyを利用してAndroid端末に物理キーボードを繋いだときのテストする

# はじめに

Android端末に物理キーボードを繋ぐようなアプリケーションを作成する場合、物理キーボードをAndroid端末に接続してテストする必要があったりしてそれらの作業がかなり面倒だったりします。そのような場合に物理キーボードを用意しなくてもテストする方法を見つけたのでまとめたいと思います。

# 前提知識

まずは物理キーボードとはなにか補足しておきます。Androidには仮想キーボードと物理キーボードの両方が使える様になっています。今回はこれらのうち物理キーボードでのテストをする方法をまとめています。

## 仮想キーボードとは

Android端末の画面上に表示されるキーボードのことで、ソフトウェアキーボードなどと呼ばれることもあります。Android端末ではTextFieldなどをフォーカスしたときなどに仮想キーボードが自動的に表示されます。

![Android 12-2023_04_02_21_13_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/695e472c-9165-c743-dbf2-b4c566647480.png)

## 物理キーボードとは

物理キーボードはAndroid端末に外部接続されたキーボードのことです。有線または無線接続でキーボードを接続をすることで、外部接続されたキーボードでTextFieldなどにて文字を入力することができます。

![Android 12-2023_04_02_21_18_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/f0bd5e9f-3df3-101c-539a-ee3729f65b2c.png)

# テスト方法

scrcpyというAndroidの画面をミラーリングするためのコマンドラインツールがあるのですが、このscrcpyにはこのscrcpyには物理キーボードをシミュレーションする`--hid-keyboard`というオプションが実装されています。

https://github.com/Genymobile/scrcpy/blob/master/doc/hid-otg.md#physical-keyboard-simulation

scrcpyをインストールしたあと、`--hid-keyboard`のオプションを付加して、scrcpyを起動することで物理キーボードを繋いだときの動作確認を簡単に実施できます。

```
scrcpy --hid-keyboard
```

しかしこのままだと仮想キーボードが表示されたままになり、物理キーボードのテストがやりづらいので、以下の手順でAndroidの設定を変更して、物理キーボードを接続した場合には仮想キーボードを表示しないようにしておきます。

1. 右下の「キーボードアイコン」をタップする
2. オプションの「Show virtual keyboard」をOFFにする

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/993f2ece-8dcc-8adf-fc03-5c53b6fcce95.gif)

これで物理キーボードがなくてもAndroid端末の実機で物理キーボードを利用したテストが簡単にできるようになります。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/2f0de5b9-f9f2-ab08-7d72-8de25b2805aa.gif)

# おわりに

上記に紹介した物理キーボードのオプション以外にもscrcpyには実機テストで、役立つオプションが実装されています。なので興味ある方はscrcpyのオプションを眺めてみると良いかなと思います。

https://github.com/Genymobile/scrcpy#user-documentation

また個人的にscrcpyのGUIツールを作っていたりします。どうしてもコマンドラインツールはとっつきづらいという人はこちらのGUIツールを利用してみてくれればと思います。

https://github.com/kaleidot725/ScrcpyHub

![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/c274884a-0c1d-4c8d-aa8c-4883dc17aa80.gif)
GraphQL クライアントではクエリインジェクションに注意しよう

# 発端
GraphQL API の利用者から API クライアントのコードレビューに付き合ってほしいと言われたので出席したんですが、とんでもない実装になっていて腰を抜かしてしまったので、注意喚起しようかなと。

# はじめに結論
- クエリの一部にユーザ入力を文字列連結するのはやめましょう！
- 動的パラメータは `variables` で指定しましょう。
  `variables` の指定例は以下を参照。
  https://graphql.org/learn/serving-over-http/#post-request
  ポピュラーな GraphQL クライアントであれば、 variables に当たる変数を渡すことができるインタフェースが用意されていると思います。

# インジェクションが起きる仕組み
実装言語は何でも良いです。
以下のような API クライアント実装があるとします。

```typescript:client.ts
const userInput = 'abc123' // これがユーザの入力値
const result = GraphQLClient.query('{ someQuery(id: "' + userInput + '") { id } }')
```

このとき、開発者は以下のようなクエリが実行されることを期待しているはずです。

```graphql
{
  someQuery(id: "abc123") {
    id
  }
}
```

しかし、もしユーザ入力が以下のような形だったら・・・
```typescript:injection_sample.ts
// こんな文字列だと...
const userInput = 'abc123") { id } users(id: "user1") { creditCardNumber } q2: someQuery(id: "abc123'
const result = GraphQLClient.query('{ someQuery(id: "' + userInput + '") { id } }')
```

こんなクエリが発行されてしまいます！
```graphql
{
  someQuery(id: "abc123") {
    id
  }
  users(id: "user1") {
    creditCardNumber # ヤバい情報にアクセス
  }
  q2: someQuery(id: "abc123") {
    id
  }
}
```

実際には、得られた実行結果をそのまま出力するようなシステムは無いと思うので、
`creditCardNumber` の問い合わせ結果を攻撃者が確認できるケースはかなり限定されると思います。
しかし、以下のような危険は依然として残っています。

- ヘビーなクエリを組み合わせて DoS 攻撃できてしまう可能性
- 実行するのが Mutation であれば、任意の mutation を呼び出せてしまう可能性

では、どうやって対策すれば良いのでしょうか？

# 対策： 文字列連結ではなく variables を使う
動的パラメータは `variables` というもので渡すようにします。

https://graphql.org/learn/serving-over-http/#post-request


`variables` を使う想定で前述のクエリを書き直すと以下のようになります。
```graphql
query($someId: ID!) {
  someQuery(id: $someId) {
    id
  }
}
```

ポピュラーな GraphQL API クライアントであれば、以下のように variables も指定できるようなインタフェースが用意されているはずです。

```typescript:good.ts
const userInput = ...
const variables = { id: userInput }
const result = GraphQLClient.query('query($id: ID!) { someQuery(id: $id) { id } }', { variables })
```

:::note warn
GraphQL API クライアントを使わずに HTTP レイヤでリクエストを組み立てる場合、文字列連結で variables の JSON を組み立てることも、同様に好ましくありません。
:::

# variables には Object を指定できる

GraphQL の呼び出しを HTTP レイヤで見ると、 `query` は (JSON っぽく見えるのに) 文字列で指定することから、 `variables` も文字列で指定すると勘違いされるかもしれませんが、 Object を指定できます。
従って、カスタムの input type を丸々 variables から与えることも可能です。

```graphql:query.graphql
mutation AddUser($input: AddUserInput!) {
  addUser(input: $input) {
    id
    errors { message }
  }
}
```
```json:variables
{
  "input": {
    "username": "Taro",
    "address": "Tokyo",
    "languages": [ "ja", "en" ]
  }
}
```


# まとめ
GraphQL API とガッツリ通信しようとするクライアントは、最初から GraphQL クライアントライブラリを導入するモチベーションが生まれやすいので、この問題に遭遇しにくいかもしれません。
逆に、公開されている GraphQL API のうち 1, 2 個しか使わないから「fetch API や curl を使って HTTP レイヤで簡単に済ましちゃえ」と思っているクライアントは罠にハマりやすいのではないかと。

発生のメカニズムは他の～～インジェクション系脆弱性と全く同じですね。
言語の境界をまたぐところにインジェクションあり、です。
SQL インジェクション対策にバインド変数を使いましょうなどと言うのと同じく、GraphQL では　variables を使いましょう。
【日報 4/11】

# 本日の目標（TODO目標/できるようになりたいこと）
- QUEST 1~6
# 学習時間（Hour）
- 目標4h 結果4h
# 目標振り返り（TODO進捗/できるようになりたいこと振り返り）
- QUEST 1~6
# 詰まっていること（実現したいこと/現状/行ったこと/仮説）
- (現状)教材を見てコードを書く
- (理想)課題を見て、コードがイメージできる
# 学んだこと（新しい気付き、学び）
- 課題は参考書等見ながら実施したので、完全に理解できていない部分もあるので、翌日以降本などで復習していきたいと思った。
# 感想（一日の感想、雑談）
- 独学でやっていた頃と比べて、やる気がでた。優秀な方も多いので遅れをとらないようにしたい。緊張感もあるが楽しさも増してる。
# 明日の目標（TODO目標/できるようになりたいこと）
- QUEST 7~9 
- Ruby入門2章
AWS Client VPN Basic ハンズオンで詰まった部分と対処法

# どんな記事か
AWS公式の『AWS Client VPN Basic ハンズオン』[(こちら)](https://catalog.us-east-1.prod.workshops.aws/workshops/be2b90c2-06a1-4ae6-84b3-c705049d2b6f/ja-JP)で躓いたところをまとめた記事です。引っかかった部分のみにフォーカスして記事にしているため、ハンズオンの構築から疎通テストまでの全手順が知りたい方は、[こちら](https://catalog.us-east-1.prod.workshops.aws/workshops/be2b90c2-06a1-4ae6-84b3-c705049d2b6f/ja-JP)をご参照ください。
構築したシステムは以下の通りです。図はハンズオンから引用しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/0e03d015-0074-1399-dcab-a2c9c5d70339.png)

# どこで躓いたのか
ハンズオン終盤でVPN接続が完了して、実際にAWSリソース(EC2)にアクセスできるかどうかpingを飛ばしてみたのですが、うまくいきませんでした。コンソールで以下は確認済みです。
* Client VPNエンドポイントの接続はAvailable
* VPN接続が確立した時点で、クライアント VPN エンドポイント IDのステータスもActive
* リソースの値や設定は間違っていない（ハンズオン通り）
* セキュリティグループ、NACLのルールも問題ない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/6eb84828-f6fa-09ff-3e25-7350c911ba9c.png)


# 結論
原因はクライアントVPNエンドポイントの承認ルールの記載内容にありました。承認ルールとは何なのか、公式ドキュメント[(こちら)](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-working-rules.html)から説明文を引用します。
>承認ルールは、ネットワークへのアクセス許可を与えるファイアウォールルールとして機能します。承認ルールを追加することで、特定のクライアントに対し、特定のネットワークへのアクセス許可を与えます。アクセス許可の対象となるネットワークそれぞれに、承認ルールが必要となります。コンソールと AWS CLI を使用して、クライアント VPN エンドポイントに承認ルールを追加できます。

クライアントVPNエンドポイントにはセキュリティグループを付与できるのですが、それとはまた別でこんな仕組みがあったのですね。ハンズオンを通して初めて知りました。

# 承認ルールの設定
では具体的に承認ルールをどう記載したかを述べます。クライアントVPNエンドポイント作成後、「承認ルール」タブから、「認証ルールを追加」を選択すると承認ルールを設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/4d8e405a-7d5e-4b35-bf65-dd447c119624.png)
その後、下のような画面が出てきます。このとき、ハンズオンでは「アクセスを有効にする送信ネットワーク」の項目に「198.19.0.0/16」と入力するように記載されていますが、これだとVPCから外に出る通信のアクセス許可が無いため、EC2からpingが返ってきません。なので代わりに「0.0.0.0/0」と入力します。

※ちなみにハンズオンと内容が似ている、AWS Skill Builderの「Configure and Deploy AWS Client VPN」[(こちら)](https://explore.skillbuilder.aws/learn/course/internal/view/elearning/40/configure-and-deploy-aws-client-vpn)の方でもここの値は「0.0.0.0/0」に設定するように指示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/927f250e-4b9e-a219-75f3-37d6cfe38f08.png)

最終的な設定は以下のようになりました。再度pingをうったところ、今度はうまくいきました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986198/3038e70e-5a16-e348-f842-99d9735bb739.png)


# 終わりに
今回は、AWS公式のハンズオンで詰まった部分とその解決策について述べました。最初は「公式ハンズオン一つできない自分ってなんなんだ…」とへこみましたが、何とか解決まで至ってよかったです。他にいい方法等あれば、コメント等いただけますと幸いです。ここまでお読みいただきありがとうございました。

# 参考URL
* [『AWS Client VPN Basic ハンズオン』](https://catalog.us-east-1.prod.workshops.aws/workshops/be2b90c2-06a1-4ae6-84b3-c705049d2b6f/ja-JP)
* [『Configure and Deploy AWS Client VPN』](https://explore.skillbuilder.aws/learn/course/internal/view/elearning/40/configure-and-deploy-aws-client-vpn)
* [『承認ルール』(AWS公式ドキュメント)](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-working-rules.html)
お手軽 Reload 可能な useFuture のラッパーを書いてみる

# 目的
[flutter_hooks](https://pub.dev/packages/flutter_hooks) における `useFuture` は、
Future インスタンスを更新しない限り非同期処理を再実行しない。

何らかのアクションをトリガーにして処理を再実行したい場合、ちょっとした工夫が必要になる。
もっとお手軽に再実行したいということで、 CustomHook を作ってみることにした。


# 活用サンプル

```dart:article_view.dart
class ArticleView extends HookConsumerWidget {
  const ArticleView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ⭐ future をお手軽に再評価可能な useFuture
    // Future<String> fetchArticle();
    final article = useReloadableFuture(() => fetchArticle());

    if (!article.hasData) {
      return CircularProgress();
    }

    return Column(
      children: [
        Text(article.data),
        ElevatedButton(
          child: const Text("Reload"),
          // ボタン押下により fetchArticle() が再実行される
          onPressed: () => article.reload(),
        ),
      ],
    );
  }
}
```

# CustomHook の実装

```dart:use_reloadable_future.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

/// reload 可能な [AsyncSnapshot] を提供する [useFuture] のラッパー
///
/// 引数は [Future] を再生成可能な [getFuture] 関数となっている。
/// snapshot.reload() の実行によって [getFuture] が再実行される。
_ReloadableAsyncSnapshot<T> useReloadableFuture<T>(
  Future<T> Function() getFuture, {
  T? initialData,
  bool preserveState = true,
}) {
  final key = useState(1);
  final future = useMemoized(getFuture, [key.value]);
  final snapshot = useFuture(
    future,
    initialData: initialData,
    preserveState: preserveState,
  );
  final reloadable = _ReloadableAsyncSnapshot(
    snapshot,
    reload: () => key.value += 1,
  );
  return reloadable;
}

/// [AsyncSnapshot] に対し、外部から実行可能な [reload] メソッドを加えるためだけのクラス
///
/// 外部で活用する想定はないため private でよい。
class _ReloadableAsyncSnapshot<T> extends AsyncSnapshot<T?> {
  _ReloadableAsyncSnapshot(
    AsyncSnapshot<T?> snapshot, {
    required this.reload,
  }) : super.withData(snapshot.connectionState, snapshot.data);

  final void Function() reload;
}
```

## 参考文献
https://stackoverflow.com/questions/70775414/how-to-reload-data-when-using-usefuture-flutter-hook
cssの優先度

CSSの優先度は、スタイルの適用範囲によって異なる値が割り当てられ、この値が大きいほど優先されます。優先度の割り当て方は以下の通りです。

1. インラインスタイル属性：`style`属性で直接指定されたスタイルには最も高い優先度が割り当てられます。

2. IDセレクター：`#`を使って指定されたIDセレクターには、インラインスタイル属性に次いで高い優先度が割り当てられます。

3. クラスセレクター、属性セレクター、擬似クラス：`.`、`[` `]`、`:`を使って指定されたクラスセレクター、属性セレクター、擬似クラスは、IDセレクターよりも優先度が低くなります。

4. 要素セレクター、擬似要素：要素名、`::`を使って指定された擬似要素は、IDセレクター、クラスセレクター、属性セレクター、擬似クラスよりも優先度が低くなります。

5. 継承されたスタイル：スタイルが指定されていない要素には、その親要素から継承されたスタイルが適用されます。継承されたスタイルには最も低い優先度が割り当てられます。

また、同じ優先度のスタイルが複数存在する場合、CSSの「カスケード」の原則に従って、後に記述されたスタイルが優先されます。ただし、インラインスタイル属性はこのルールの影響を受けません。

それでは、各項目の具体例をいくつか挙げてみます。

1. インラインスタイル属性の例：

```
<div style="color: red;">This text is red.</div>
```

この例では、`div`要素の`style`属性で`color`プロパティに`red`という値が直接指定されています。

2. IDセレクターの例：

```
<div id="my-div">This text is in a div with the ID "my-div".</div>

#my-div {
  color: blue;
}
```

この例では、`div`要素に`id`属性で`my-div`という値が指定されています。その後、`#my-div`というセレクターで、`color`プロパティに`blue`という値が指定されています。この場合、インラインスタイル属性が存在しないため、`#my-div`によって指定されたスタイルが適用されます。

3. クラスセレクターの例：

```
<div class="my-class">This text is in a div with the class "my-class".</div>

.my-class {
  color: green;
}
```

この例では、`div`要素に`class`属性で`my-class`という値が指定されています。その後、`.my-class`というセレクターで、`color`プロパティに`green`という値が指定されています。この場合、インラインスタイル属性が存在しないため、`.my-class`によって指定されたスタイルが適用されます。

4. 要素セレクターの例：

```
<div>This text is in a div.</div>

div {
  font-size: 20px;
}
```

この例では、`div`要素に対して、`font-size`プロパティに`20px`という値が指定されています。この場合、インラインスタイル属性やクラスセレクターが存在しないため、要素セレクターによって指定されたスタイルが適用されます。

5. 継承されたスタイルの例：

```
<div>
  <p>This text is in a paragraph.</p>
</div>

div {
  color: purple;
}
```

この例では、`div`要素には`color`プロパティに`purple`という値が指定されています。その下にある`p`要素には、スタイルが指定されていません。この場合、`p`要素には`color: purple;`が継承されるため、文字の色が紫色になります。
wemosで俺言語 その2

# 概要
wemosで俺言語やってみる。
練習問題、やってみる。
構想編。

# 練習問題
web越しに、俺言語を実行する装置を発明せよ。

# 方針
- ノートパソコン、アンドロイドタブレットのブラウザから操作する。
- wemosのipアドレスは、シリアルコンソールで確認する。
- webserverをポート80で、立てる。
- 一行毎に実行する。
- formをpostする。

- lcdkeypadを検討する。

```


const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;  // arduino
const int rs = 0, en = 2, d4 = 4, d5 = 14, d6 = 12, d7 = 13; // Wemos D1 
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);// arduino
LiquidCrystal lcd(0, 2, 4, 14, 12, 13);// wemos d1

```

以上。
【React】子コンポーネントでの状態変更を親コンポーネントに検知させたい

# はじめに
やりたいこととしては、データ(ここではtest)の一部(ここではstatus)が変更されると画面に表示されているデータが最新の状態になるようにしたい。
現状はstatusの変更を子コンポーネントで行っており、変更後のデータが画面に表示されていませんでした。

# 解決方法
- コールバック関数を用いて子コンポーネントのstateの変更を親コンポーネントに検知させる（reactは単方向データバインディングなので親子間の双方向な設計は良くないようです）

親コンポーネントのstateの変更であれば子コンポーネントも自動でレンダリングしてくれるのですが、
子コンポーネントのstateの変更は親コンポーネントで検知できないため、自動でレンダリングが走らず画面が更新されないことが原因でした


# 親コンポーネント(App.tsx)

```tsx
// import省略

export type Data = { title: string; status: number };
function App() {

  const [test, setTest] = useState<Data[]>([
    { title: "1", status: 0 },
    { title: "2", status: 1 },
    { title: "3", status: 2 },
  ]);

  const handleStatusChange = (index: number, status: number) => {
    const updatedTest = [...test];
    updatedTest[index].status = status;
    setTest(updatedTest);
  };

  useEffect(() => {
    console.log("useEffect");
  }, [test]);

  return (
    <Box h="95vh">
      <HStack spacing="1px" h="90%">
        <Title label="未着手" status={0} datas={test} onStatusChange={handleStatusChange} />
        <Title label="着手中" status={1}  datas={test} onStatusChange={handleStatusChange} />
        <Title label="完了" status={2}  datas={test} onStatusChange={handleStatusChange} />
      </HStack>
    </Box>
  );
}

export default App;

```

# 子コンポーネント(Title.tsx)

```.tsx
// import省略

type Props = { label: string; status: number; datas: Data[], onStatusChange: (index: number, status: number) => void };

export const Title = (props: Props) => {
  const { label, status, datas, onStatusChange } = props;
  const [cards, setCards] = useState<Data[]>(datas);

  const clickRight = (data: Data, index: number) => {
    data.status = data.status + 1;
    setCards([...datas])
    onStatusChange(index, data.status);
  };
  
  useEffect(() => {
    console.log("Title.tsx");
  }, [cards]);

  return (
    <Box w="33%" h="100%" bg="gray.100" onClick={() => console.log(cards)}>
      <Box
        h="5%"
        bg="gray.400"
        display="flex"
        alignItems="center"
        justifyContent="center"
      >
        <Box my="2"> {label}</Box>
      </Box>
      {cards?.map((data,index) =>
        data.status === status ? (
          <Box h="10%" my="3">
            <Box
              h="100%"
              w="80%"
              margin="auto"
              bg="gray.200"
              display="flex"
              alignItems="center"
              justifyContent="center"
            >
              {data.title}
              <Button h="1.75rem" size="sm">
                <FaChevronRight onClick={() => clickRight(data, index)} />
              </Button>
            </Box>
          </Box>
        ) : (
          ""
        )
      )}
    </Box>
  );
};

```

## ポイント
- handleStatusChangeというコールバック関数を宣言しています。この関数で各test毎にstatusの変更をstateで監視します。
- 子コンポーネントでこの関数を呼び出すと、親で監視しているstateが変更されるため子から親への検知が可能になります。

# おわりに
ファイルの分け方に伴う親子間のpropsの考え方なども理解を深めていきたいです。
AWS - ECS のコンテナに ECS Exec で接続する例

# 準備


- 何らかの方法でAWS認証をしておく
- AWS CLI をインストールしておく
- Session Manager をインストールしておく ( https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html )

もしくは AWS Cloud Shell から試すのが手軽

# 実行

`ecs-cli ps --cluster [ECSのクラスタ名]` でサービスの一覧を確認する


```
ecs-cli ps --cluster ecs-cluster-name

Name                                                    State                Ports                     TaskDefinition            Health
ecs-cluster-name/xxxxxxxxxxxxxxxxxxxxxx/container-name  RUNNING              ***********************   **********                HEALTHY
```

この表示だと何がどれか分かりづらいが、上の一覧でいうと

- クラスタ名: `ecs-cluster-name`
- タスク名: `ecs-cluster-name/xxxxxxxxxxxxxxxxxxxxxx`
- コンテナ名 `container-name`

という対応。

この中のRUNNING のタスクに対してコマンド実行する

例:

```
aws ecs execute-command --cluster ecs-cluster-name --task ecs-cluster-name/xxxxxxxxxxxxxxxxxxxxxx --container container-name --interactive --command "/bin/sh"
```



# チャットメンバー募集


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

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


# Twitter

https://twitter.com/YumaInaura
Cloud Firestoreでpythonでも集計クエリ（count）を使用する

2022年10月にFirestoreでcount()が使用できるようになるアナウンスされました。
https://cloud.google.com/blog/ja/products/databases/aggregating-data-with-firestore

しかし、pythonでも使えることを本記事の執筆時点ではまだあまり情報が少ない状況です。
https://cloud.google.com/firestore/docs/query-data/aggregation-queries?hl=ja
![スクリーンショット_2023-04-13_17_22_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/22a63346-7816-a0ca-7751-7d44a7541728.png)

調べると、2023年2月のアップデートで利用できるようになっています。
https://firebase.google.com/support/release-notes/admin/python#version_610_-_02_february_2023
![スクリーンショット_2023-04-13_17_24_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/9445879a-dd92-d68e-8758-0c853a922a8a.png)


実際に検証して確かにCOUNT可能でしたので、備忘録として残しておきます。

## 環境

まずはfirebase-adminをcountが使えるようになった`v6.1.0`にアップデートしましょう。

```
% python -V
Python 3.8.13

% pip list | grep firebase-admin        
firebase-admin                 6.1.0

% pip list | grep google-cloud-firestore
google-cloud-firestore         2.11.0
```

## クエリしてみる

まずは何らかのコレクションリファレンスに対してqueryした状態です。

```python
query = collection_ref.where("status", "==", True)

# <class 'google.cloud.firestore_v1.query.Query'>
print(type(query))
```

このqueryに対してcount()が使えるようになりました。
get()で取得出来ます。

```python
count_get = query.count().get()
# [[<Aggregation alias=field_1, value=4, readtime=2023-04-13 08:36:05.086885+00:00>]]
print(count_get)
```

このままですとcountした数として扱えないので、以下のようにvalueだけ取り出します。

```python
for count in count_get:
    # [<Aggregation alias=field_1, value=4, readtime=2023-04-13 08:36:05.086885+00:00>]
    print(count)
    # 4
    print(count[0].value)
    # <class 'int'>
    print(type(count[0].value))
```

## まとめ
簡単にまとめすぎましたが、普段pythonでFirestoreを利用している方なら要点のみご理解いただける内容になっているかと思います。

料金も一致したインデックス エントリの数に対して課金され、最大1000件で1回分の読み取りとなるようなので、積極的に使っていきたい料金体系ですね。

トランザクションやAggregationには一切深堀りしていないので、また機会ある時に追記したいと思います。

【ソースコード】
https://github.com/googleapis/python-firestore

【公式リファレンス】
https://cloud.google.com/python/docs/reference/firestore/latest/query#countalias-strhttpspythonreadthedocsioenlatestlibrarystdtypeshtmlstr--nonehttpspythonreadthedocsioenlatestlibraryconstantshtmlnone--none
AWS 基本ネットワーク構成(VPCやらサブネットやら)

# 個人的リマインド用
参考
[【AWS 入門】基本ネットワーク構成をマスターしよう！](https://www.youtube.com/watch?v=7J_crzU5KP8)
[ルーティングを超ざっくり解説！【何となく理解するIT】](https://www.youtube.com/watch?v=69ejrlRAu0Q)
[サブネットマスクとCIDRを超ざっくり解説！【何となく理解するIT】](https://www.youtube.com/watch?v=28C3Djjx6ew)

## 出てくるいろんな言葉たち
・VPC
・サブネット
・インターネットゲートウェイ
・NATゲートウェイ
・ルートテーブル

## VPC(Virtual Private Cloud)
一番外枠にあるやつ
AWS上で仮想のネットワーク領域を作れる(どんなアプリを作るにしてもこの中に色々入れて作っていく)

**どういう風に作るか**
サブネットマスクのCIDRで切る(???)
例：172.16.1.0/24みたいな感じ

### (補足)サブネットマスクとCIDR(サイダー)
**1.サブネットマスク**
ネットワーク(IPアドレス)の範囲を表せるようなもの
例：172.16.10.0~172.16.10.255というアドレス帯の範囲を示したい時
↓(サブネットマスクを使うと)
172.16.10.0/255.255.255.0(255~のとこがサブネットマスク)という感じに表せる

**何がいいの？**
アドレス帯の目的は0~255という数字を表したいのが目的
→サブネットマスクではそれがぱっと見で分かる

(ちなみに)
255を2進数にすると11111111(8bit),0は00000000(8bit)
サブネットマスクの255の3連単はそれぞれ8bitずつ与えられているということ

**2.CIDR**
255255ばっかで分かりにくいっていう時に役立つのがCIDR(CIDR表記ともいう+サブネットマスクよりこっちの方がいい、業務でよく使われる)
255.255.255.0
↓(CIDR表記では)
24と表す(172.16.10.0/24という感じ)

**なぜ24**
255は2進数で1が8個。255255255だと1が24個。

/20の場合は11111111.11111111.11110000.00000000(0が12個余ってる、2進数だと2の12乗個余ってる(4096個))
つまりは172.16.0.0 ~ 172.16.15.255までが表せる(難しい…)

## サブネット
VPCの中をさらに細かく分ける単位
VPCを作ったなら必ず1つ以上のサブネットが必要

例：今回はパブリックサブネット(172.16.1.0/25)とプライベートサブネット(172.16.1.126/25)の2つに分割

**※大事！！**
**パブリックサブネット**→インターネットから入れるし出れる領域を想定して作ってる(ブラウザから叩いてすぐ入れる、IGWを通る)
**プライベートサブネット**→インターネットに出れる領域(IGWを通って出る)

→実際にAWSに上の名前でサブネットを設定するわけではなく、デフォルトゲートウェイがIGWに設定してあるルートテーブルをもったサブネットをパブリックサブネットという。また、それ以外をプライベートサブネットという。

## インターネットゲートウェイ(IGW)
VPCの先端、入り口みたいなとこがIGW(インターネットゲートウェイ→これをVPCにつけることをアタッチという)
これをアタッチすることで外の世界との通信が可能になる

## NATゲートウェイ
NATとはIPアドレスの変換。NATゲートウェイはNATをする。プライベートサブネット内のものは基本プライベートIPアドレスしか持たないが、NATすることで外でも使えるように。一方外から来ても、グローバルなIPアドレスを持たないから入ってこれない。

## ルートテーブル
ルーティングの設定を行みたいに設定して、サブネット単位でくっつけることが出来る(パブリックサブネット用ルートテーブル、プライベートサブネット用ルートテーブルみたいな感じで作る)

パブリックサブネットに関しては、デフォルトゲートウェイをIGWに向ける。
プラーベトサブネットのデフォルトゲートウェイは、パブリックサブネット内にあるNATゲートウェイに向く。ちなみにNATゲートウェイはIGWに行く。

### (補足)ルーティング
ルーティングは通信の宛先を決定する
例：192.168.1.0/24にサーバーが2台、192.168.2.0/24にサーバーが1台
同じネットワークだと通信の行き来が自由(httpのリクエストとか自由に投げれる)

別のネットワークと通信したい時に間に**ルータ**を置く
ルータの左側には192.168.1.254、右側には192.168.2.0/252を設定(それぞれ別のネットワークに所属しているよ)

＊＊具体的に＊＊
宛先： (別ネットワークのサーバーのアドレス)
ネクストホップ： (ルータの自分側アドレス)

固定した宛先でなくてもそっちのネットワークに行け！みたいにアバウトな感じで送れる

**ちなみに**
ネットワークからインターネットに出たい時はどうするか
→0.0.0.0/0(デフォルトゲートウェイ、全てのIPアドレスを指定する)を設定し、それとは別に宛先の指定を行う(スタティックルーティング、個別に指定しなくてもいい)
Justで並行して複数コマンドを実行する方法

Justを使用して、並行して複数のコマンドを実行する方法を解説します。下記のサンプルコードを軸にステップバイステップで説明していきます。

```ruby:justfile
dev:
  #!/bin/bash -eux
  cmd1 &
  cmd2 &
  trap 'kill $(jobs -pr)' EXIT
  wait
```

## ステップ1: スクリプトの宣言

```bash
#!/bin/bash -eux
```

この行は、スクリプトを実行するシェルを指定しています。`-eux`オプションは以下の意味を持ちます。

- `-e`: 途中でエラーが発生した場合、スクリプトを終了します。
- `-u`: 未定義の変数を参照した場合、スクリプトを終了します。
- `-x`: 実行するコマンドを表示します。デバッグに便利です。

## ステップ2: コマンドをバックグラウンドで実行

```bash
cmd1 &
cmd2 &
```

`&`をコマンドの後ろにつけることで、コマンドをバックグラウンドで実行できます。これにより、`cmd1`と`cmd2`が同時に実行されます。

## ステップ3: スクリプト終了時にバックグラウンドのコマンドを終了する

```bash
trap 'kill $(jobs -pr)' EXIT
```

`trap`コマンドは、指定されたシグナルが発生したときに実行するコマンドを設定します。ここでは、`EXIT`シグナルが発生したときに、`kill $(jobs -pr)`を実行します。`jobs -pr`は、現在のシェルで実行中のバックグラウンドプロセスのプロセスIDを一覧表示するコマンドです。このスクリプトでは、`cmd1`と`cmd2`のプロセスIDがリストされます。

## ステップ4: バックグラウンドのコマンドが終了するのを待つ

```bash
wait
```

`wait`コマンドは、バックグラウンドで実行中のすべてのコマンドが終了するのを待ちます。これにより、`cmd1`と`cmd2`が並行実行され、両方が終了するまでスクリプトが終了しなくなります。

このように、Justを使用してシンプルなスクリプトで複数のコマンドを並行して実行する方法を説明しました。これを活用して、効率良くタスクを実行しましょう。

今回の投稿では、Justを使用して並行して複数のコマンドを実行する方法を解説しました。これを応用して、開発や運用作業を効率化することができます。Justは柔軟なツールであり、他にも様々な使い方がありますので、ぜひチャレンジしてみてください。

参考になれば幸いです。
laravel マイグレーションロールバックのステップを指定して実行する

# 概要

- マイグレーションロールバックにて、ステップ数を指定してロールバックさせる方法をまとめる。
- laravelのマイグレーションロールバックは特に指定しないと前回マイグレーション分をすべてロールバックしてしまう。

# 方法

- `--step=ステップ数`のオプションを使い、ロールバックするマイグレーションの数を指定する。
- 4ステップだけロールバックしたい場合、下記を実行する。

    ```shell
    php artisan migrate:rollback --step=4
    ```

- そもそも現在のマイグレーションの状況を知りたい場合、下記を実行する。

    ```shell
    php artisan migrate:status
    ```
GKE+ArgoCDでSSL化する方法

GKEでArgoCDのserverをSSL化する方法がわからず、やっとSSL化できたので、やり方を紹介する。

https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/



## 手順

### Argocd-serverのdeploymentのcontainer.comandの引数に`--insecure`の引数を入れてあげる。
k editとかでyamlファイルを書き換えてください。これはArgoCD側で`http→https`にリダイレクトするのをOFFにしています。
sslリダイレクトはGKE側で行わないといけないので、この`--insecure`が必要です。

```shell
k get deploy argocd-server -oyaml -n argocd
```

![スクリーンショット 2023-04-13 16.45.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466470/01854554-610d-f01c-d94f-0c714dcd77f3.png)



### Argocd-serverのSVCを立てる。

```yaml
apiVersion: v1
kind: Service 
metadata:
  annotations:
    cloud.google.com/neg: '{"ingress": true}'
    cloud.google.com/backend-config: '{"ports": {"http":"argocd-backend-config"}}'　//下記でbackendConfigをapplyする
  name: argocd-server
  namespace: argocd
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server
  type: ClusterIP
```

### GKE特有のBackendConfigとFrontendConfigをapply

frontendConfig→Ingress
backendConfig→serviceに対応してる。

https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features?hl=ja


```yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: argocd-backend-config
  namespace: argocd
spec:
  healthCheck:
    checkIntervalSec: 30
    timeoutSec: 5
    healthyThreshold: 1
    unhealthyThreshold: 2
    type: HTTP
    requestPath: /healthz
    port: 8080
---
apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
  name: argocd-frontend-config
  namespace: argocd
spec:
  redirectToHttps:
    enabled: true
```
### 静的IPを作成し、CloudDNSのレコードにAレコードとして登録する。

これはterraformから行ってもok。静的IPを作成して、作成されたIPアドレスをCloudDNSの作成したホストゾーンに
Aレコードで登録する。

### IngressをApplyする
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    kubernetes.io/ingress.global-static-ip-name: argocd-global-ip //作成した静的IPアドレス
    networking.gke.io/managed-certificates: argocd-cert //下記で使用するManagedCertificateを紐付ける。
    kubernetes.io/ingress.class: "gce"
spec:
  defaultBackend:
    service:
      name: argocd-server
      port:
        number: 80
---
apiVersion: networking.gke.io/v1
kind: ManagedCertificate //GKEマネージドリソースで自動でSSL証明書を発行してくれる。
metadata:
  name: argocd-cert
  namespace: argocd
spec:
  domains:
    - ホストゾーンに登録したサブドメイン
```

### 少し時間が経つとhttpsでアクセスできるようになります。

`kind:ManagedCertificate`で作成したSSL証明書。

![スクリーンショット 2023-04-13 16.48.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466470/80ce817c-2be0-402e-8f15-69613c71f69a.png)

argocd用のingressの様子。
![スクリーンショット 2023-04-13 16.50.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466470/5331f320-f5ca-0938-0162-b9386b8a9765.png)
Prime Speciality Clinic: Your Destination for the Best Laparoscopic Surgery in Gwalior

If you're looking for the best laparoscopic surgery in Gwalior, Prime Speciality Clinic is the right choice for you. Our experienced laparoscopic surgeons are skilled in performing a wide range of procedures using the latest technology and techniques.

At Prime Speciality Clinic, we understand the importance of providing compassionate and personalized care to our patients. That's why we work closely with each patient to develop a customized treatment plan that meets their individual needs and goals.Whether you need laparoscopic surgery for digestive disorders, gynecological issues, or any other condition, we're here to help. Our team of dedicated medical professionals is committed to ensuring that our patients receive the highest quality care and support.

To read more :- https://primespecialityclinic.org/laparoscopic-surgery/
GCSにファイルをアップロードする

# 事前準備
1. Cloud StorageにBucketを作成する
2. Cloud Storageへの書き込み権限を持つサービスアカウントを作成する
3. 作成したサービスアカウントのクレデンシャルファイル（JSON）をローカルに保存する

# 構成
`main.py`は実行コード、`sample.json`はアップロードしたいファイル、`<your-credential-file>.json`は認証に必要なファイル（事前準備3で取得したファイル）です。
```
$ tree
.
├── main.py
├── sample.json
└── <your-credential-file>.json
```

# コード
事前に、`pip`を使用して必要なライブラリをインストールします。
```
$ pip install --upgrade google-cloud-storage
```

以下のコードがサンプルです。
```python:main.py
from google.cloud import storage
import json

credential_file = './<your-credential-file>.json'

client = storage.Client.from_service_account_json(json_credentials_path=credential_file)

# 作成したバケットの名前を指定します
bucket = storage.Bucket(client, '<your-bucket-name>')

blob = bucket.blob('uploaded_sample.json')
blob.upload_from_filename('sample.json')
```

これで、`python main.py`を実行すると、作成したバケットにファイルがアップロードされます。

## 環境変数を使用する場合
通常、クレデンシャルなどのファイルを直接リポジトリに保持するのは避けられるので、事前準備で取得したJSONファイルの内容を環境変数として読み込む方法です。

事前に環境変数にJSONの値を登録します。
```shell
$ export GOOGLE_APPLICATIOON_CREDENTIALS_JSON='<JSONの中身>'
```

先ほどのコードを以下のように修正します。
```diff_python:main.py
from google.cloud import storage
import os
import json

- credential_file = './sandbox01-281123-99edfe2e0eb7.json'
+ json_str = os.environ['GOOGLE_APPLICATION_CREDENTIALS_JSON']
+ credentials = json.loads(json_str)

- client = storage.Client.from_service_account_json(json_credentials_path=credential_file)
+ client = storage.Client.from_service_account_info(credentials)

bucket = storage.Bucket(client, 'monitoring-ai-bucket')

blob = bucket.blob('uploaded_sample2.json')
blob.upload_from_filename('sample.json')

```

以上です。
【VBA】PostgreSQL用のORM (っぽいもの)

# はじめに

Excel VBAを使ったアプリケーションにおいて、当方ではデータベースとしてPostgreSQLを使用している
ただ、毎回コネクションしたりSQL書いたりと面倒くさい事が多い

Pythonでいうところの「Sqlalchemy」のようなORMがあれば便利だがVBAでそんなものは無く、
無いならばとORM(っぽいもの)を作成したので公開します

https://github.com/HirotoYoshizawa/PostgresORM.git

詳しく使い方についてはdocumentation.mdを読んで下さい

# 先にお詫び

PostgresORM風なものを作ったには作ったのだがSQLでいうJOIN句のように、2つ以上のテーブルを接続する機能が実装出来ていない
(そこまでやる気力が無かった)

# 最後に

今回作成したORM風プログラムはクラスモジュールを理解する上で良い勉強になりました
ニーズとしてはかなりニッチなものとは思いますが使いにくいところや、もっと良いコードの書き方があるなどあれば、是非フィードバック頂けると嬉しいです
ROS シミュレーションを使いこなす！

ROSのシミュレーション的なものには，rvizとgazeboの2種類がある．それらを使いこなすため，チュートリアル的に簡単なロボットモデルを作成しました．

「シミュレーション的なもの」と言っているのは，rvizのシミュレーション（ロボットの動作）はただ見た目が動いているだけ（衝突判定や重力などの「シミュレーション」計算は行われていない）からです．
正式には　*"3D visualization tool for ROS"*　なので，「シミュレーションっぽいけど，実はシミュレーションじゃない」という意味で「シミュレーション的なもの」と呼んでいます．

二つの使い分けとしては，gazeboでシミュレーションしたロボットの情報（カメラや姿勢に関するのセンサ情報，周辺地図）をrvizにロボットモデルごと表示したり，rvizからGUIでgazebo上のロボットの動きを制御したりするイメージ．

※私も使ってみてようやく違いを理解したので，そういうものなんだな～と思ってくれればいいです．


# rviz
rvizとは，ロボとの位置や速度などの情報を３次元で可視化するrosツールの一つ．[参考サイト](https://qiita.com/srs/items/5f44440afea0eb616b4a#%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E7%B7%A81gazebo%E5%9F%BA%E7%A4%8E)の手順に従って学びました．

詳細は☟のページにて解説．

https://qiita.com/Karin-Sugi/items/5bfbaf7cca164e04773e

URDFで記述する際，同じような記述を繰り返し入力する必要がある場合がある．☝のサイトでは練習がてら繰り返したが，実際はめっちゃ手間．
その手間を省略するために，xacro（xml macro）という記法がある．gazeboのurdfは部品ごとにxacroで書いた（参考サイト：[xacroを使う１](https://qiita.com/srs/items/43528d00ee789171367f)，[２](https://qiita.com/srs/items/9ac7f2f6e47732bb7535)）


# gazebo
gazeboは物理シミュレータであり，本来rosからは全く独立したもの．一方でrosとの接続性がよく一緒に使われることも多い（公式でも公認されている）．

以下の参考サイトの手順を織り交ぜながら学んでいきました．
<table>
  <thead>
    <tr>
      <th>参考サイト</th> <th>URL</th>
    </tr>
  </thead>
  <tr>
    <td> ROS講座（目次） </td><td>https://qiita.com/srs/items/5f44440afea0eb616b4a#%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E7%B7%A81gazebo%E5%9F%BA%E7%A4%8E</td>
  </tr>
  <tr>
    <td> gazebo + ros </td> <td>https://qiita.com/RyodoTanaka/items/174e82f06b10f9885265</td>
  </tr>
  <tr>
    <td> 九工大blog </td> <td>http://cir-kit.github.io/blog/categories/build-a-robot/</td>
  </tr>
  <tr>
    <td> クワマイでもできる </td> <td>https://kuwamai.hatenablog.com/entry/2019/03/31/211826</td>
  </tr>
</table>

詳細は☟のページにて解説．

https://qiita.com/Karin-Sugi/items/4918168649a8fb9b35d3
table puls上からカラムを追加する

# 概要

- table puls上からカラムを追加する方法を簡単にまとめる。

# 方法

1. 左部メニューの「Tables」にあるカラムを追加したいテーブル名を右クリックする。

    ![account_book___laravel_apache_-_MySQL_8_0_32_と_app_php_—_temposmart-next-src.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/bcffa716-1e59-1f75-2e1a-d9d5b90c630e.png)

1. 「Open structure」をクリックする。
1. 下部の「+ Column」をクリックする。

    ![account_book___laravel_apache_-_MySQL_8_0_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/7bce3d5b-ec86-6183-6ed1-b30432c94b50.png)

1. 追加された行にカラムの情報をそれぞれ入力し「cmd」+「s」で保存する。
リリース後の監視をはじめてみた

こんにちは。元々はフロントエンドをやっていましたが最近はSREの [とよしー](https://twitter.com/_toyo_sea_) です。

読者のみなさんは物件をリリースした後ってログの監視をしていますでしょうか？
リリース直後のプロセスって具体的な他社事例をあまり聞いたことがないので気になっています。

プロダクトによって違うとは思いますが、今回は弊社のリリース直後のプロセスについて書いてみます。
弊社では週に1回のペースで開発/修正物件をリリースをしています。
そこでリリースした後に監視してみてはどうかと考え、まずは自分だけでもはじめてみました。

## なぜやるか？(Why)
端的に言うと、エラーが発生したあとの初動を早くするためです。
「切り戻しが必要なのか / hotfixで修正する必要があるのか」といった判断を早くできるように障害検知までの時間を早くしたいのです。
このプロセスがうまくいけばMTTR（平均修復時間）を短くなることにつながるのではないかと考えています。

## リリース後の監視って？
DatadogのAPMとRUM（Real User Monitoring）を用いてエラーの発生状況を監視することを指しています。
APMではLiveでエラーログを監視し、RUMではLiveの設定がないので直近5分のエラーログの発生状況を見るようにしています。

## どのようにやるか？(How)
エンジニアが全員いるSlackチャンネルでDatadogのAPMとRUMのURLを共有
↓
監視する
（とはいえ常に凝視しているわけではなく検知できるようにページを常に開いているイメージです）
↓
見慣れないエラーログが出た場合はエンジニアに共有
↓
障害につながりそうな場合は切り戻し/hotfixで修正の判断をする
↓
おおよそリリースしてから30〜45分ほど見て影響がなければ監視は終了

## やってみてどうだったか？
約2ヶ月ほどやってみて思ったことです。

1. 浸透が今後の課題
リリース後の監視自体はとくに難しい要素はないので誰でもできると思います。
課題としては、リリース後の監視というプロセスをどう組織に浸透させるか。
浸透させるにはさまざまな乗り越えないといけない壁があります。
例えば、Datadogの使い方がわからないから監視を避けてる方がいるかもしれないし、そもそも監視に意味を見出せていない人もいるかもしれない。
それらを乗り越えていくにはDatadogの使い方をドキュメンテーションしたり、監視の意義やメリットを繰り返し伝えていく必要がありそうです。
まずは共感してくれる人を増やしながら、草の根活動のようにやっていこうと思っています。

2. エラーログのノイズが多いことがわかった
はじめたころは、RUMでものすごい量のconsole.errorが出ており、リリース起因のエラーが調べるのが大変でした。
しかも、そのものすごい量のconsole.errorを調べるとユーザー影響がないことがわかり、ロギングの仕方の問題であることがわかりました。
このままだとエラーログのノイズが多いので、監視のハードルがあがってしまいます。
まずは、ユーザー影響はないがロギングの仕方が問題となっている一番多いconsole.errorを修正することにしました。
監視をすることで、あるべきロギングのルールを考えることができたのは収穫でした。

3. アラートを出せばよいのでは...
一方でエラーログを検知したらアラートをSlackで通知すればよくね？と思う瞬間もあったのですが、
アラートを作れば自動的に解決するわけでもないのが難しいところだと思っています。
結局アラートが日常的に検知できる環境だと、開発者がアラート疲れ/アラート慣れ してしまうことも懸念していました。
やがてオオカミ少年のようなアラートになるのではないかと。
そのためリリース後の一定時間というタイミングのみ、エラーにフォーカスして監視したほうがMTTRの短縮に貢献できるのではないかと考えています。
（正直に言うと、いまだにアラートの扱い方には頭を悩ませています）

4. エラーログとして出ない障害は検知しにくい
例えば、
「このデータってユーザーに見えていいんだっけ？ 制限がかかっていないとダメだよね？」
というエラーパターンは、エラーログを吐かせにくいので検知が難しいと感じました。
そもそも論としてこのようなエラーは設計や実装などのもっと早いフェーズで検知されるべきではあるのですが、
リリース後も見つけやすい仕組みを考えていけると良いなあと思いました。


以上、リリース後の監視についてでした！
読んでくださりありがとうございます。
Raspberry Pi 2BでUSBドングル型WiFiインターフェイスを使う（Elecom WDC-150SU2MBK）

# やったこと
Raspberry Pi 2BでUSBドングル型WiFiインターフェイス（[Elecom WDC-150SU2MBK](https://www.amazon.co.jp/gp/product/B00JTSF7DY/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&th=1)）を使ってWiFi接続をしてみました。

# ドライバーインストールは不要
WDC-150SU2MBKにはRTL8188EUSというデバイスが搭載されていて、ググると「ドライバーを入れよ」という情報がありますが**やる必要ありません**。最新のRaspbian OS（Linux raspberrypi 5.10.103-v7+ #1529 を使用 2023.04.13）だとデフォルトで対応しているようです。**デバイスをUSBポートに挿すだけで認識されます**（青い光が点滅しているはず）。

# NetworkManagerをインストール
認識はされますが、SSIDの設定ができないので、以下のコマンドでNetworkManagerをインストールします。再起動すると、上のタスクバーに新しいアイコンが追加されてWiFiのSSIDが設定できるようになります。
```
sudo apt install network-manager network-manager-gnome
```

# 所感
世の中いろんなものが簡単になっているということですね。
nodeをfedora37にインストール

## 概要

公式のレポジトリを追加すればインストールできそうと思ったけれど、公式では36までしか対応していない。
そのため、公式サイトのLTSのバイナリをダウンロードしてそれをインストールした。

## インストール

ダウンロードしたディレクトリへ移動して解凍。ダウンロードはブラウザから行った。

```sh
tar Jxf node-v18.16.0-linux-x64.tar.xz
```

私の場合はこの解凍されたディレクトリへのシンボリックリンクを作成し、それから解凍されたディレクトリ内のbinのパスを追加するという感じにした。

```sh
ln -s /path/to/node_dir /path/to/s_link
```

.bash_profileの編集

```sh
export PATH="$HOME/path/to/s_link/bin:$PATH"
```

## 動作確認

以下のコマンドでバージョンを確認できた。

```sh
node -v
v18.16.0
```

```sh
npm -v
9.5.1
```
【SwiftPM】自作パッケージに外部パッケージの依存関係を追加する方法

# はじめに
Swiftパッケージマネージャーで、自作パッケージに外部パッケージの依存関係を追加する方法を紹介します。

# STEP 1: Package.swift ファイルの開く
まず、自作パッケージの Package.swift ファイルを開きます。

# STEP 2: 追加するパッケージを指定
Package.swift ファイルの dependencies プロパティに、追加したいパッケージを指定します。以下の例では、swift-dependencies パッケージを追加しています（ややこしくてごめんなさい🙇）。

```swift
dependencies: [
    .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.2.0"),
],
```

url パラメータには追加したいパッケージの URL を指定し、from パラメータには使用したいバージョンを指定します。

# STEP 3: targets プロパティに依存関係を追加
依存関係を追加するために、targets プロパティの適切なターゲットに依存関係を追加します。以下の例では、 StationRepository ターゲットに swift-dependencies パッケージを追加しています。

```swift
.target(
    name: "YourPackage",
    dependencies: [
        .product(name: "Dependencies", package: "swift-dependencies")
    ]),
```

.target メソッドの dependencies パラメータに、パッケージを指定します。.product メソッドのnameにはプロダクト名（import 〇〇の〇〇の部分）、packageにはパッケージ名を記します。

# STEP 4: テストターゲットにも追加
テストターゲットでも使用する場合は、こちらにも依存関係を追加します。

```swift
.testTarget(
    name: "YourPackageTests",
    dependencies: [
        "YourPackage",
        .product(name: "Dependencies", package: "swift-dependencies")
    ]),
```

# 全体のコード
```swift
import PackageDescription

let package = Package(
    name: "YourPackage",
    platforms: [
        .iOS(.v16)
    ],
    products: [
        .library(
            name: "YourPackage",
            targets: ["YourPackage"]),
    ],
    dependencies: [
        .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.2.0"),
    ],
    targets: [
        .target(
            name: "YourPackage",
            dependencies: [
                .product(name: "Dependencies", package: "swift-dependencies")
            ]),
        .testTarget(
            name: "YourPackageTests",
            dependencies: [
                "YourPackage",
                .product(name: "Dependencies", package: "swift-dependencies")
            ]),
    ]
)
```

---

ご覧いただきありがとうございました。

🐦 [Twitter: @shota_appdev](https://twitter.com/shota_appdev)

📱 [個人アプリ: Symbols Explorer](https://apps.apple.com/jp/app/symbols-explorer/id1612933742)
laravel エラーでartisan コマンドが実行できない

# 概要

- ブランチを移動後に`$ php artisan migrate`を実行しようとしたところ、「プロバイダが見つからない」系のエラーで実行できなかったので解決方法をまとめておく。

# 問題までの経緯

1. feature/Aブランチにて開発を実施
1. `App/Providers/FooServiceProvider.php`を追加しFooServiceProviderクラスを定義した。
1. `config/app.php`のprovidersの配列にFooServiceProviderを追記した。
1. 下記コマンドを実行してconfigのキャッシュクリアを実施した。

    ```shell
    php artisan config:clear
    ```

1. 何度かテストコードを実行したりしてfeature/Aブランチで開発を行った。

# 問題

- developブランチに移動し、下記コマンドを実行してマイグレーションを試みた。

    ```shell
    php artisan migrate
    ```

- 下記のエラーが発生した。

    ```
    Class 'App\Providers\FooServiceProvider' not found
    ```

# 解決までの経緯

1. developブランチにて`config/app.php`のproviders配列を見ても、FooServiceProviderは記載されていない。そのため本当に当該のサービスプロバイダクラスが存在していなくてエラーになっている可能性は低い。
1. おそらく`config/app.php`の状態がキャッシュされているためそのキャッシュにFooServiceProviderの呼び出しが記載されているのかも知れない。developブランチでconfigキャッシュをクリアしてみる。
1. developブランチで`$ php artisan config:clear`を実行しようとしたところ、また下記のエラーが出てキャッシュクリアできない。

    ```
    Class 'App\Providers\FooServiceProvider' not found
    ```

1. 一旦feature/Aブランチに移動する。
1. `$ php artisan config:clear`を実行する。
1. developブランチに移動しマイグレーションを実行したところエラーは出ずマイグレーションができた。
GithubでTodo管理をしている方向け：issueを自動的にProjectに追加する方法

## 目的
Githubのissues×Projectでタスク管理をされている方向けに、自動的に対象Projectにissueを追加する方法を記載


## 従来の課題
- issueを起案する際に、自動的にProjectがセットされない
- 故に、手動でProjectをつける必要があり、毎回となると割と手間

## 従来の課題（capture）
- 自動的にProjectがセットされてほしいが、通常は手動選択
- 対応策として、.githubフォルダにcustom.mdファイルを起き、自動的にセッティングする手法もあったが、現在では使えない模様

例）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274358/5bf7b1d0-3391-e58a-f0a1-7d7b8166eff6.png)


## Sol:Projectのworkflowを利用して条件がマッチするissueを自動的にprojectに追加する
### 手順
1. 対象のProjectには入る
2. Workflowsを押下
3. Auto-add to projectを押下
4. Editを押下
5. `is:issue,pr is:open`を入力-> Openなissueを自動的にProjectに追加する意味
6. Save and turn on workflowsを押下

以上の手順で、次回のissue起票から自動的に対象のProjectにissueが追加されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274358/989d7abf-6ddc-850e-6955-362477b15250.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274358/8c9537f2-96b4-e1c5-6814-e8837d673293.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274358/163dc33a-ab6a-e559-815a-7237823efcaa.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274358/0f421d66-f876-3efe-2d03-9ff01301558a.png)
Swift Compiler Error (Xcode): Cannot find 'PiPViewCoordinator' in scope

## はじめに
jitsiMeetのexampleコードをビルドするときにエラーが起きたので共有します。

## エラー内容
```
Swift Compiler Error (Xcode): Cannot find 'PiPViewCoordinator' in scope
```

## 解決法
podfileの下の方にある記述を以下のように書き換える。

```podfile:Podfile
post_install do |installer| 
  installer.pods_project.targets.each do |target|
  flutter_additional_ios_build_settings(target)
      target.build_configurations.each do |config|
        config.build_settings['ENABLE_BITCODE'] = 'NO'
        config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
         end
     end
   end
```

## 参考
https://stackoverflow.com/questions/71304369/cannot-find-type-pipviewcoordinator-in-scope
パーミッション権限の設定方法について

【背景】
システム制作において、権限の設定をする業務を任された。

【必要な権限】
・カメラ
・push通知
・ストレージ
・広告トラッキング

【一旦関連リンクまとめ】
設定方法は以下のリンクにまとまっている

(android)
・カメラ
https://qiita.com/SallyCinnamon/items/4e793bdcb70df2c21813
・push通知
https://zenn.dev/nekoniki/scraps/fd21c393af733c
・ストレージ
https://techgrowup.net/2021/05/14/permission/
・広告トラッキング
https://qiita.com/kokogento/items/eb53b68da8a42e7a4266

(ios)
・カメラ
https://qiita.com/SallyCinnamon/items/4e793bdcb70df2c21813
・push通知
https://zenn.dev/chocoyama/articles/5b07eead5ae0aa
https://repro.io/contents/ios-remote-push-notifications-in-a-nutshell/
・ストレージ
https://majintools.com/2018/11/05/photo/
・広告トラッキング
https://zenn.dev/yorifuji/articles/ios-app-tracking-transparency

【権限の設定ファイル】
ios・・・info.plist
android・・・AndroidManifest.xml

【使用ライブラリ】
react-native-permissions
https://qiita.com/SallyCinnamon/items/4e793bdcb70df2c21813

【エラー対応】
・バージョンは3.8.0を使用しますが、エラーが出るので、以下対応
https://github.com/facebook/react-native/issues/34106
↓↓
iOS フォルダーで、Pods/Pods.xcodeproj/xcuserdata/project.pbxproj に移動します。

すべての「IPHONEOS_DEPLOYMENT_TARGET = 11.0」を「IPHONEOS_DEPLOYMENT_TARGET = 12.4」に変更します。保存して実行します。
↑↑

・起動できない
https://stackoverflow.com/questions/61980130/console-error-no-permission-handler-detected-react-native

↓↓
permissions_path = '../node_modules/react-native-permissions/ios'

pod 'Permission-AppTrackingTransparency', :path => "#{permissions_path}/AppTrackingTransparency"
↑↑

iosは上記で上手くいくが、androidは上手くいかなかった。(ad_idがライブラリにないため)
以下によりandroidは実施
https://reactnative.dev/docs/permissionsandroid

※広告IDは、googleのplay consoleで検討する予定。
Neo4jのアップグレードとデータ移行まとめ

Neo4jのアップグレードとデータ移行をまとめます。まずは公式サイトのアップグレードガイドに従ってアップグレードを実施し、次にデータのエクスポートとインポートを行います。

# 【アップグレード】
1. 公式サイトの[ダウンロードページ](https://neo4j.com/download/) から最新バージョンのNeo4jをダウンロードします。
2. 既存のNeo4jインスタンスを停止し、データをバックアップします。
3. 新しいバージョンのNeo4jをインストールします。
4. 旧バージョンから新バージョンへの移行に必要な設定変更を行います。詳細については公式ドキュメントの[アップグレードガイド](https://neo4j.com/docs/operations-manual/current/upgrade/) を参照してください。

# 【データ移行】
データのエクスポートとインポートには、以下の手順を実行します。

1. 既存のNeo4jインスタンスで、データをエクスポートします。Cypherシェルを使用して、データベースをCSVファイルにエクスポートするか、APOCプロシージャを使用してグラフデータをエクスポートできます。

    CSVエクスポート:
    ```console
    CALL apoc.export.csv.query('MATCH (n) RETURN n', '/path/to/export.csv', {})
    ```

    グラフエクスポート:
    ```console
    CALL apoc.export.graphml.all('/path/to/export.graphml', {})
    ```

2. 新しいNeo4jインスタンスで、データをインポートします。エクスポートしたCSVファイルやグラフデータを使用して、データをインポートできます。

    CSVインポート:
    ```sql
    LOAD CSV WITH HEADERS FROM 'file:///path/to/export.csv' AS row
    CREATE (n:Label {property: row.property})
    ```
    
    グラフインポート:
     ```console
    CALL apoc.import.graphml('/path/to/export.graphml', {readLabels: true})
    ```

3. インポートが完了したら、新しいNeo4jインスタンスを起動し、データが正常に移行されたことを確認します。

これらの手順に従って、Neo4jのアップグレードとデータ移行を実施できます。最新バージョンにアップグレードすることで、機能追加やパフォーマンス向上、セキュリティ強化が得られます。


## 注意
この記事はChatGPTで作成してみたものなので、何か手違いがあればお知らせください。
.NET MAUIでFrame、BoxViewのOpacityが機能しない

# 原因
現在.NET MAUI側のバグでBoxView、FrameなどでOpacityが正しく機能しないらしい。

# 動作環境
.NET7.0

# 補足
```MainPage.xaml
<ContentPage
    x:Class="MauiApp1.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

    <BoxView
        BackgroundColor="Black"
        HeightRequest="100"
        Opacity="0.2"
        WidthRequest="100" />

</ContentPage>
```

Windows
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958553/fd7bba13-321d-55e3-ac61-c15222554717.png)


Android
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958553/f9e4c889-4616-363b-64e1-cac138f3d59d.png)

簡易的なコードで確認しました。
画像を見てわかると思いますが、WindowsではOpacityは正しく機能しますが、AndroidではOpacityは正しく機能しないようです。
.NET MAUI側のバグでいつ修正されるかはわからないです。

.NET MAUIで移行していた際につまづいたので共有しておきます。
Blazor Desktop で、ページ ( html / razor ) の Style を C# で保存するようにした

Blazor Desktop のスタイル設定を、ローカルの json ファイルに保存したり読み出したりしたいと思ったが、
JavaScript の fetch 関数は使えないようなので、下記のようにした。

###### 前提
今回は body 要素の backgroundcolor と forecolor の設定を読み書きする。

下記の内容の settings.json がある事。(色は何でもいい。)
```
{"bodyBackgroundColor":"#000000","bodyForeColor":"#000000"}
```

###### 設定ファイル読み取り時
C# で JSON ファイルを読み取って JavaScript に渡してスタイルを適用する。
```C#
// JSON ファイルから設定を読み取って、JavaScript の applySettings 関数に渡す。
protected override async void OnInitialized()
{
    var text = await File.ReadAllTextAsync(@"D:\settings.json", System.Text.Encoding.UTF8);
    await js.InvokeAsync<string>("applySettings", text);
}
```

```JavaScript
// 設定を保持するオブジェクト
var settings = {
    "bodyBackgroundColor": "",
    "bodyForeColor": ""
}

// C# から呼び出されて、settings に保存してから、画面に反映。
// 引数は JSON 形式の文字列。
function applySettings(settingJsonString) {
    var obj = JSON.parse(settingJsonString);

    settings.bodyBackgroundColor = obj.bodyBackgroundColor;
    settings.bodyForeColor = obj.bodyForeColor;

    document.getElementsByTagName("body")[0].style.backgroundColor = settings.bodyBackgroundColor;
    document.getElementsByTagName("body")[0].style.color = settings.bodyForeColor;
}
```

###### 設定保存時
JavaScript から C# に、JSON 文字列化した設定を渡して保存する。

```JavaScript
// C# から呼び出された時に、設定を JSON 化して渡す関数
function getSettings() {
    return JSON.stringify(settings);
}
```

```C#
// 保存するサンプル書く為だけなので、OnAfterRender にしているが、保存のタイミングは好みで。
// JavaScript から受け取った JSON 形式のテキストをファイルに保存する。
protected override async void OnAfterRender(bool firstRender)
{
    var text = await js.InvokeAsync<string>("getSettings");
    await File.WriteAllTextAsync(@"D:\settings.json", text, System.Text.Encoding.UTF8);
}
```
モバイルアプリでファイルボタンが表示されない


単にページレイアウトにボタンを追加しただけでは表示されませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/76d9db78-9a66-da04-17e6-62c3bad74058.png)

以下が参考になりました。

https://developer.salesforce.com/forums/?id=9060G0000005rDJQAY


:::note
I had the same issue. I was able to upload from desktop, the File button was in Mobile and Lightning Experience Actions, but the File button was not showing in mobile. I was also able to upload files on other objects. Taylor Harper's solution is correct.

In Setup go to Feed Tracking > Your Object > Check Enable Feed Tracking and Save. 

The file upload button will now appear when you're on the record. 
:::
ERROR: (gcloud.sql.import.sql) HTTPError 403: The service account does not have the required permissions for the bucket.

サービスアカウントを表示してCloud storageに閲覧権限を付与しましょ
```bash
gcloud sql instances describe $instance --format="value(serviceAccountEmailAddress)"
```



https://stackoverflow.com/questions/50828098/permissions-for-google-cloud-sql-import-using-service-accounts
EC2内のRedisにローカル環境から接続したい

## 状況
- パブリックサブネットにEC2インスタンスを置いている
- EC2にはRedisをインストールしている
- EC2からSSH接続できるようにしている

## やりたいこと
手元のPC（ローカル環境）からEC2を経由してRedisに接続したい

## 対応内容
ローカル環境からEC2にSSH接続できる場合、SSHトンネリングを使用してRedisにアクセスすることができます。SSHトンネリングを使用すると、ローカル環境からEC2を経由してRedisにアクセスすることができます。

## 手順
1. 以下のSSHコマンドを実行します
```
$ ssh -i <秘密鍵ファイルのパス> -L 16379:127.0.0.1:6379 ec2-user@<EC2パブリックIPアドレス>
```
- <>内のところは環境に合わせて書き換えてください
- ローカルで既にポート:16379を使っている場合は他の番号（26379など）に書き換えてください

2. クライアントツールを使ってローカル環境からアクセスします。
  ホスト名に`localhost`、ポートには手順1で入力した番号を指定してください

以上で接続するまでの手順です
【新人プログラマ応援】エンジニアにとって大切だと思うこと【ポエム】

こんにちは。船井総研デジタルのいっちーです。

エンジニアとして新たに社会人の道を歩み始めた皆様、おめでとうございます。

今回はいわゆるポエムの書き飛ばしになりますが、これからエンジニアの道を歩んでいく若い人たちに伝えておきたい…というか、私が新人のときにこういう言葉が欲しかったなぁ…ということを語ってみようと思います。

:::note info
本稿で「エンジニア」とは、いわゆる「ITエンジニア」を指すものとします。とはいえ、ITエンジニア以外の方にも当てはまることはあると思いますが…。
:::

# エンジニアにとって最も必要なスキルとは？
プログラミングの知識とか、英語とか数学とか、色々大事なスキルがあると思いますが、私はまず **「国語力」** を挙げたいと思います。この商売、技術書、仕様書、ソースコードなど、仕事として色々なドキュメントを読み書きするということが日常的に発生しますので、何よりもまず国語力を鍛えておくことが大切と思います。特に、社内外の技術に明るくない方と会話するとき、専門用語をわかりやすく説明するには、国語力が欠かせません。
また、きちんとした国語力に裏打ちされてこそ、英語や数学、プログラミング言語といった他の分野の知識が学習でき、活きてくると思います。技術書ではなくても良いので、本でも新聞でも、文章のプロが書いた文章を読む習慣はつけておきましょう。

# 技術書を買おう
ネットで最新の情報が手に入る時代にはなりましたが、それでもできるだけ紙の書籍を使って勉強することが大切と思います。書籍は出版社による校正を通っていますので、ある程度情報の品質が保証されています。また、下品な広告で気を散らされたり、「ここから先は有料会員様のみご覧いただけます」と肝心な情報が隠してあったり、目障りな全画面広告に切り替わってイライラさせられるといったことがないので、精神衛生上とても良い。

さらに、調べ物をしてパラパラめくるたびに、関連する用語が必ず目に入ってくるので、「これ、この本のどこに書いてあったぞ」というリファレンスが勝手に頭の中に作られていきます。赤ペンで自ら入れた下線や注釈も、かけがえのない財産になるでしょう。これはネットや電子書籍では、たぶん得るのが難しい効果だと思います。
最近の書籍は、出版社のウェブサイトや著者のGithubからサンプルコードが取得できたりもします。これは私がエンジニアとして働き始めた頃にはあまり無かった(大抵CD-ROMでの配布でした)もので、その頃から比べると現在の学習環境は格段に良くなっています。

# 仕事に関係する法令の知識は持っておこう
労働基準法をはじめとするいわゆる労働三法、民法(の特に契約に関する部分)、著作権法、下請法、派遣法、電気通信事業法など、エンジニアの業務を取り巻く法律は結構たくさんあります。その全てを条文単位で細かく知っておく必要はないですが、それぞれの法律の **立法趣旨** は最低限理解しておいたほうが良いと思います。
法律で決着をつける…という事態が無いに越したことはないですが、いざという時に損をしたり、トラブルに巻き込まれないためにも、自分の仕事に関係する法律は知っておきましょう。

# エンジニアが絶対にやってはいけない、「技術の〇〇」2つ
## 技術の悪用はするな
まぁ、これは当然ですね。仕事をしていると、個人情報や機密情報といったデリケートなものを取り扱う機会も増えてきます。故意に悪いことをしないのはもちろんのこと、「知らなかったことによる過失」で会社やクライアントに迷惑をかけることがないように、日々勉強や行動の見直しは怠らないようにしましょう。

## 技術の安売りはするな
エンジニアは自分の技術…言うなれば自分自身そのものが商品です。自分の市場価値は年収幾らぐらいに相当するのか、定期的に棚卸しをして、自分自身に適正な値札を付けましょう。
この考えは、研修や先輩の指導から離れてクライアントと仕事をするようになったときに、特に意識しておくべきことだと思います。クライアントからのチャージは幾らぐらいなのか、給料・年収は市場価値相応なのか、第三者的な目線で自分を評価しましょう。
本来の市場価値に比べて著しく安い給料に甘んじてしまうと、適正な給料を受け取っている他の多くのエンジニアに仕事が回らなくなってしまい、業界全体に迷惑がかかったり回り回って自分の首を絞める事になってしまいます。市場価値と比較して自分の年収が著しく低いのであれば、自分自身のためだけではなく、他の多くのエンジニアのためにも、勇気を持って会社を変えることを検討しましょう。

# そんなこんなで
IT土方なんて自嘲したり馬鹿にされたりすることも多いこの仕事ですが、何だかんだ言っても私はエンジニアの仕事が結構好きで、ずっと続けていきたいと思ってます。若い方たちのこれからのエンジニア人生に幸多からんことを、心より祈ります。

それではまた。