OmniAICreator/Qiita-1.07M huggingface dataset:
s4hanaのスペース（ページ）にタイル（アプリ）を追加する方法

# やりたいこと
前回は自分専用のスペースを作ったので、

（スペースの作り方に興味ある方はこちらへどうぞ）

https://qiita.com/curlneko/items/ebfdf4a579fbd7d1cdfa

今回はそのスペースにアプリを入れたいと思います。

# やり方
### ①ホームから、作った「スペース」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/824a0d45-d027-87ad-1ff8-a5d4891d74cc.png)

「ページ」に遷移すると、最初はこんな感じで空っぽで、アプリが一つもない状態

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/980bb17f-be26-99a9-8102-4485d480ef64.png)


### ②右上のアイコンをクリックし、「Edit Current Page」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/6020d0e1-f908-7d83-ae00-67c9ac1de878.png)

### ③「Add Tile」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/ca28ab0d-bd29-b005-663f-22c4c7bd0660.png)

※「セクション」が一つも出てない場合は、まず「Add Section」からして「Add Tile」をする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/cff987d0-1893-386c-cc2a-322aff7c8a33.png)

### ④左バーからタイルを作ったときに選んだカタログをクリックし、タイルを選択
※今回は「Accounts Payable - Configuration」カタログの中にある「帳票出力アプリ」を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/918e9502-ef17-aa65-fbb5-56caf264c01d.png)


### ⑤前画面へ戻る
左上の＜をクリックして前画面へ戻り

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/d23b03b7-bf8f-ab77-59a4-e64bcd19f0f9.png)

すると、選んだタイル（アプリ）が出てきました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/ee39d14d-72bd-cd84-118a-ada45e4ecb67.png)

### ⑥「close」をクリック し、完了！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/d26ccd30-5125-5bd8-93fb-5098b5c4c8c4.png)

ページの編集が完了したので、今後この「スペース」に入ったら、アプリがすぐ見れます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/ed49c28c-b35c-5153-59ef-3b00a4017265.png)

# 終わりに
私の理解として、「スペース」は「スペースを見る権限（ロール）」を持っている人全員が、このスペースにアクセスできるが、

「スペース」の下にある「ページ」は全員それぞれが独立で、好きにアプリを入れられても他者には影響しない。

要は、AさんとBさんは同時に「スペース」に入ったとしても、表示するページが別々で、Aさんがページにアプリを追加しても、Bさんのページには出てこないということです。
REST APIとは？基本的な概念と利用方法

前回の[【備忘】APIとは]("https://qiita.com/MaSi1031/items/0611d7b88ae85f878515")でWeb APIの種類として【**REST API**】と【**SOAP API**】がある旨を記載しました。
今回はREST APIについて、調べてみたのでアウトプットがてら記載します。

## 結論
REST APIは、REST（Representational State Transfer）の原則に基づいて設計されたAPIのこと。
Webシステムを外部から利用するためのAPIであり、HTTPで定義するGET、POST、PUT、DELETEなどのリクエストでデータを操作できるもの。

## 詳細
### RESTとは
RESTは、「Representational State Transfer」の略でシンプルなWebシステムの設計思想のことです。
Representational →具象化された
State →状態の
Transfer →転送

ザックリお伝えすると、**具体的に状態を定義した情報のやり取り**のような意味合いになる。

### RESTの4原則とは
RESTには「**RESTの4原則**」というものがあり、これを満たすものを「RESTfulなシステム」と呼ばれる。

RESTの４原則は以下
1.統一インターフェース
2.アドレス可能性
3.接続性
4.ステートレス性

#### 統一インターフェース
統一インターフェースは、あらかじめ定義・共有された方法でやり取りされること。

例えば、「GET・POST・PUT・DELETE等のHTTPメソッドでやりとりする」や「やり取りするデータはJSON形式にする」といったもの。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303394/8f24f01d-0ed5-f5b8-ffe0-c067fbff1f95.png)


#### アドレス可能性
全ての情報が一意なURI（識別子）を持っていて、提供する情報をURIで表現できること。
このURIはUniformed Resource Identifierの略で、Webの場合は通常URLで与えられる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303394/16214d36-d8bb-e061-17b6-9134539bcc3a.png)


#### 接続性
やりとりされる情報にはハイパーリンクを含めることができる、というもの。
１つのリンクから別の情報にリンク(接続)することができて、RESTfulなシステム同士なら円滑に情報連携を行うことが可能

#### ステートレス性
ステートレス＝状態がない
つまり、RESTでは「やりとりが１回ごとに完結する」ということ。


## 使用例
・**ソーシャルメディアAPI**: TwitterやFacebookなどのソーシャルメディアプラットフォームは、REST APIを使用してユーザーデータや投稿を取得したり、投稿を行ったりすることが可能

・**天気情報API**: 多くの気象サービスプロバイダーは、REST APIを通じてリアルタイムの天気情報や予報データを提供している。これらのAPIを使用すると、アプリケーションやウェブサイトで天気情報を表示することが可能。

・**マップや地理情報API**: Google MapsやMapboxなどのマップサービスは、REST APIを使用して地図データやルート情報を提供。これを使用すると、アプリケーション内で地図を表示したり、経路案内機能を実装したりすることが可能。

・**支払いゲートウェイAPI**: StripeやPayPalなどの支払いゲートウェイサービスは、REST APIを使用して支払い処理を実施。これを使用すると、アプリケーション内で安全かつスムーズな支払い処理を実装することが可能。

・**クラウドストレージAPI**: Amazon S3やGoogle Cloud Storageなどのクラウドストレージプロバイダーは、REST APIを使用してファイルのアップロードやダウンロード、ファイルの管理などを行うことが可能。

## まとめ
**REST API＝RESTの4原則に則ったAPI**である。

※参考情報
https://tech.012grp.co.jp/entry/rest_api_basics
UnityEditor+Pico4のプレイモード中にPico4のコントローラーをトラッキングさせる

# はじめに
UnityEditor上でPico4×OpenXRのデバッグをする際にPico4用コントローラーのプロファイルがなかったので動作するものがあるのか確かめてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/57e076f8-d12b-34c2-5095-dd3ab9052463.png)

# 結論
一番上の `ValveIndexControllerProfile` を有効にするとPico4のコントローラーがUnityEditor上で動作するようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/4e9e0c39-e245-378a-1e74-58250352b381.png)
JenkinsのPipelineからPythonを実行したらcommand not found

## Pythonをパスを指定して実行したらcommand not foundと言われた
JenkinsのPipelineからパスを指定してPythonを実行したら
command not foundと言われた
```
${HOME}/.pyenv/shims/python ./test.py


/opt/homebrew/Cellar/pyenv/x.x.x/libexec/pyenv-which: line 61: basename: command not found
```

## Pythonのバージョンが違う？
```shell
python --version
Python 3.9.13

pyenv which python
${HOME}/.pyenv/versions/3.9.13/bin/python
```
バージョンは合ってそうな気がします。

## 試しにpyenv which pythonの方のPythonを使ってみる
```shell
${HOME}/.pyenv/versions/3.9.13/bin/python ./test.py

Finished: SUCCESS
```
こっちだと成功しました。何故。

## JenkinsのPATHを通す
パスを指定してそのまま使っても良かったのですが
Pythonのバージョンを更新した時に、全部のjobに手を入れるのは大変面倒くさいので
JenkinsにPATHを通しました。

Jenkinsの管理 > システムの設定 > グローバル プロパティ > 環境変数
![スクリーンショット 2024-02-06 16.39.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681368/266b019e-7726-6eb3-af6c-6f6b5dda75c9.png)


### PATHを通したので最終的にこうしました
```shell
python ./test.py

Finished: SUCCESS
```
Asterisk & fail2ban (Ubuntu) でハマったところを備忘録的に共有

## デフォルトで設定あるって情報あるのに動かないんだけど！
```ruby:/etc/fail2ban/jail.local
[DEFAULT]
（略）

[asterisk]
enabled = true

[sshd]
(後略）
```
書き換え、ヨシッ！
`systemctl start fail2ban.service`
`systemctl status fail2ban.service`
＞＞＞`State: failed`＜＜＜
→お前がfailしとるやないかい！

原因を探るため、探検隊は/etc/fail2banの奥地へ赴いた…
→paths-default.conf, paths-debian.conf 関係なかった
→filter.d/asterisk.conf 問題なさそう
→jail.conf  
```ruby:/etc/fail2ban/jail.conf
（前略）
#
# Miscellaneous
#

[asterisk]

port     = 5060,5061
action_  = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
           %(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath  = /var/log/asterisk/messages
maxretry = 10


[freeswitch]
（後略）
```
あれっ！logpathがおかしい！

末尾に.logを入れて正しいパスにしたら動きましたとさ
`logpath = /var/log/asterisk/messages.log`

おしまい！

# 追加DLC（）
### よく見たらfilter.dの中身chan_sip用でPJSIPに使えないやんけ！
 AORで弾かれたIPをBANする正規表現
```^Rejecting '<HOST>' due to a failure to pass ACL.*``` をfailregexに追加。
接続元IPが限定されているならとりあえず**最低限**は弾ける。これだけじゃ不十分だぞ！
他はWIP。WIPだから聞かんでクレメンス。
消えてくれないループ　removeするとindexが詰められる

Adobe Extendscriptを書いていて、選択したものを消したいのに消えない、でもいくつかだけ消える。ということがあり、実は当たり前のことに気づいていなかったので、自分メモ


```js:元のコード
var doc = app.activeDocument;
var sel = doc.selection;

for(var i=0; i < sel.length; i++) {
    sel[i].remove();
}
```
これで全部消えるだろうと思っていましたが、消えませんでした。

```js:正解のコード
var doc = app.activeDocument;
var sel = doc.selection;

while (sel.length > 0) {
    sel[0].remove();
}
```
または
```js:正解のコード(2)
for (var i = sel.length - 1; i >= 0; i--) {
    sel[i].remove();
}
```

`sel[n]`を`remove()`すると、`sel[n+1]`だったものが`sel[n]`になるため、`i`を0から増加させてループしても全部は消えないということでした。
VScode拡張機能の「SQLite」で発生したエラーと対処方法

## エラー発生までの経緯
大学も春休みに突入したので，「よーし，SQLの勉強するぞ！」と息巻いてvscodeを開き，最新版の<a href="https://www.sqlite.org/download.html">SQLite</a>と拡張機能として提供されている<a href="https://marketplace.visualstudio.com/items?itemName=alexcvzz.vscode-sqlite">SQLite</a>をインストールした．
まず初めに`SQLITE EXPLORER`なるものを召喚せねばならないらしく，適当に作った`test.db`を右クリックして`Open Database`を選択して...

さっそく問題にぶちあたった．
``` 
[15:04:36][vscode-sqlite][ERROR] Failed to open database 'c:\Users\hoge\code\sq\test.db': Parse error near line 4: no such column: table
  aster                                 WHERE (type="table" OR type="view")     
                                      error here ---^
```
えええ，なにこれ．
４行目にエラーとか言われても，そもそもまだ４行目とかないんですけど.
というか，`Where`クエリなんて書いてないよ．


## 解消までの経緯
ということで，githubで開発元の<a href="https://github.com/AlexCovizzi/vscode-sqlite/issues/235">プルリク</a>を見て前例がないかを見てみると意外といっぱいありました（笑）
同時に，何人かの方が原因と解決策を提示していらしたのでここにまとめます．

## 原因と解消まで
### 原因
拡張機能でインストールされる`"C:\Users\hoge\.vscode\extensions\alexcvzz.vscode-sqlite-0.14.1\dist\extension.js"`に原因が潜んでいたようです．SQLiteが3.41.0からダブルクォーテーションによる記述をサポートしなくなったらしく，エラーに含まれていたような`WHERE (type=\"table\" OR type=\"view\")`というクエリは受け付けなくなったようです．したがって，このダブルクォーテーションをシングルクォーテーションへ変更することが求められているようです．

### step1
この`extension.js`をvscodeで開くと膨大なソースコードが展開されますが，臆さずにナビゲーションバーから**編集**を選択し，真ん中くらいにある**検索**を選択してください．

### step2
出てきた検索バーに`\"table\"`とでも入れてやると該当箇所がハイライトされるので，その付近にあるはずの`WHERE (type=\"table\" OR type=\"view\")`を`WHERE (type=\'table\' OR type=\'view\')`としてあげます．これで準備は整ったので，一度ウィンドウを閉じたら再度開いて少し待ちます．

### step3
データベースファイルを右クリック，`Open Database`を選択すると今度はしっかりエクスプローラーウィンドウの最下に`SQLITE EXPLORER`が出てきました．

:::note info
出てこなかった場合
もう一度ファイルを開いて修正したコードになっているかを確認してください．
また，変更の反映までに時間がかかる場合もあります．
:::

やっと拡張機能が使えるぞ！

## 結論

正直，素人目にはそんなに大それたエラーではないし，「早く治してほしーなー」といった感じです．今回の内容で不明点・矛盾・誤りがあったら申し訳ありません．
Rustをデプロイするとerror while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directoryエラーが出る

# はじめに

Kubernetesのジョブが起動しなくなったのでまとめます

# 問題

ArgoCDでRustのジョブをデプロイしたところ以下のエラーが出ました

```
/usr/local/bin/sample-job: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 03:22:07 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8
```

ここで`libssl-dev`をいれてみたけど改善はしませんでした

# 回避方法

Rustのバージョンを上げていたことが問題でした

`rust:1.65.0` -> `rust:1.75.0`

今回はバージョンを戻すことで解決しました(??)

# おわりに

今回は解決方法ではなく回避方法をのせました。緊急だったので解決方法はみつけていません
わかるかたぜひコメントください
【Azure】BotpressのDBをsqlite ⇒ PostgreSQL にするときにつまずいた話

# はじめに
`Botpress`サーバーを立てる際は、デフォルトで内部の`sqlite`を使用して様々なデータを管理しています。
しかし、早いうちに`PostgreSQL`を使用するよう[公式サイト](http://botpress-docs.s3-website-us-east-1.amazonaws.com/docs/infrastructure/database/)より推奨されています。
耐障害性、安定性、拡張性、安全性が上がるから、とのこと。良いことだらけですね。

本記事では、Azure環境で上記のようなデータベースの移行を行おうとした際に激詰まりした内容を、戒めがてら紹介します。

※Botpressサーバーを立ち上げるまでの流れは[こちら](https://qiita.com/tz77/items/9bc58a5e6efa3773fb9b)で紹介しております。

# 発生したエラー
今回は`Azure Container Registry`に保存したDockerイメージを使用して、`Azure App Service`にBotpressサーバーを立てておりました。

簡単な構成図は以下です。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337466/3d31f333-7915-c978-3d0a-d7bc0dd2aed8.png)

ここから、`Azure Database for PostgreSQL`を使用し、データベースを外部に移そうと試みました。
移行後の構成図は以下のようになります。
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337466/46b7c6b5-211c-7fd4-c6db-45cd1bf369b4.png)

移行する方法は[公式サイト](http://botpress-docs.s3-website-us-east-1.amazonaws.com/docs/infrastructure/database/)に記載しているように、`App Service`の「構成 > アプリケーション設定」に以下のパラメータを追加します。
|  key  |  value  |
| ---- | ---- |
|  DATABASE  |  postgres  |
|  DATABASE_URL  |  postgres://dbuser:password@your-db-host.com:5432/your-db-name  |

※データベース側のファイヤーウォール設定などは割愛します。

これらの設定を行い再起動しましたが、以下のようなエラーが発生しました。
```
error: no pg_hba.conf entry for host "XX.XXX.XX.XXX", user "postgres", database "botpressdb", no encryption
```
`XX.XXX.XX.XXX`はApp ServiceのIPアドレスです。

`no encryption`とあるように、通信を行う際に暗号化が行われておらず、接続できないとのことです。

# 結論
接続文字列に`ssl=true`というパラメータを追加します。
|  key  |  value  |
| ---- | ---- |
|  DATABASE  |  postgres  |
|  DATABASE_URL  |  postgres://dbuser:password@your-db-host.com:5432/your-db-name?ssl=true  |

# もう少し詳しく
`Azure Database for PostgreSQL`では、デフォルトでクライアント側にSSLを使用した暗号化を使用した通信を強制します。
（[公式](https://learn.microsoft.com/ja-jp/azure/postgresql/flexible-server/how-to-connect-tls-ssl)より。）

こちらのトラブルシューティングでも同じような記載があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337466/f00e3230-cfb9-4222-70a4-86b37871642b.png)

なので暗号化を行うよう調整すればよいとアタリはつきましたが、具体的な方法が分からず右往左往しておりました、、。

ところで、この「暗号化の強制」は解除することができます。
上記のキャプチャにもある通り、「PostreSQL > サーバーパラメーター > require_secure_transport」をOFFにすることで暗号化を使用しない通信ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337466/2d648d6e-d389-8182-f508-8620bd3cc661.png)

こちらをOFFにして接続ができた場合は、接続文字列自体は合っていると判断できるかと思います。

# 最後に
今回のエラーの解決のためにかなりの時間を費やしてしまいました、、。
だれかの助けになれば幸いです。

# ご紹介
株式会社ジールでは、「ITリテラシーがない」「初期費用がかけられない」「親切・丁寧な支援がほしい」「ノーコード・ローコードがよい」「運用・保守の手間をかけられない」などのお客様の声を受けて、オールインワン型データ活用プラットフォーム「ZEUSCloud」を月額利用料にてご提供しております。
ご興味がある方は是非下記のリンクをご覧ください：
https://www.zdh.co.jp/products-services/cloud-data/zeuscloud/?utm_source=qiita&utm_medium=referral&utm_campaign=qiita_zeuscloud_content-area
時刻変換そのに

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

private static String convertToCustomFormat(String inputIsoDateTime) {
    try {
        // カスタムフォーマッターを定義
        DateTimeFormatter customParser = new DateTimeFormatterBuilder()
                .appendPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .optionalStart().appendPattern("XXX").optionalEnd() // "+09:00" の形式
                .optionalStart().appendPattern("XX").optionalEnd()  // "+0900" の形式
                .optionalStart().appendPattern("X").optionalEnd()   // "+09" の形式
                .toFormatter();

        ZonedDateTime dateTime = ZonedDateTime.parse(inputIsoDateTime, customParser);
        
        // 変換後のフォーマット
        DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss,SSSSSSXXX");
        
        return dateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo")).format(customFormatter);
    } catch (DateTimeParseException e) {
        // 解析に失敗した場合のエラーメッセージ
        return "Failed to parse date time: " + inputIsoDateTime;
    }
}


または

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.Locale;

private static final DateTimeFormatter CUSTOM_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
        // 日付と時間をパースするための基本的なパターン
        .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
        // 秒の小数部分（ミリ秒）はオプショナル
        .optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd()
        // タイムゾーンのオフセットはオプショナル
        .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
        .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
        .optionalStart().appendOffset("+HH", "Z").optionalEnd()
        // パースする際のスタイルを設定（厳格にする）
        .toFormatter(Locale.US).withResolverStyle(ResolverStyle.STRICT);

public static String convertToCustomFormat(String inputIsoDateTime) {
    try {
        // 入力された日時を解析
        ZonedDateTime parsedDateTime = ZonedDateTime.parse(inputIsoDateTime, CUSTOM_DATE_TIME_FORMATTER);

        // 日本標準時に変換
        ZonedDateTime dateTimeInJST = parsedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));

        // 指定されたフォーマットで出力
        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
        return dateTimeInJST.format(outputFormatter);
    } catch (Exception e) {
        // 解析またはフォーマットに失敗した場合のエラーメッセージ
        return "Failed to parse date time: " + inputIsoDateTime + " Error: " + e.getMessage();
    }
}
サイトリサーチ 01

▼サイト名
雑貨屋HAPPENING

▼URL
https://happening.store/

▼このサイトについての分析
全体的にデコラティブでユニークな印象を受けた。
おそらくゴシック体のフォント、バウンスのかかったアニメーション、彩度等からそれらを感じ取ったのではないか。

背景が青色で、グラフィックデザインっぽくなりそうなところ、余白を大きめにとってWebサイトらしさが出ているようにしているように感じた。
社名の「HAPPENING」感をデザイン全体を通して感じることができた。ちゃんとサイトの概要とデザインが結びついているのがとても良いと思った。
Thrusterを楽しみにしている

先日、Rails8のマイルストーンが発表された。

その中でAirlockという謎のワードがでていたのをご存知だろうか

Railsのフロントエンドは7以降import-mapsを使ったstimulusコントローラのオートローダーを導入している

基本的に何もしなければRailsアプリケーションはTurboによって最初からSPA化されてるということもあり、このローダーはデフォルトで初回表示時にimport-maps内に記載されてるStimulusコントローラの全ダウンロードを実施する

https://github.com/hotwired/stimulus-rails#:~:text=controllers%20are%20pinned.-,By%20default%2C%20your%20application,controllers%2Findex.js%20file.,-When%20lazy%20loading

この読み込みを高速化するため、Rails7系以降はhttp/2化がパフォーマンス上の重要な要件となっている

https://x.com/dhh/status/1737566892343365776?s=20

こういった需要に応えるべくRailsがアプリケーションサーバーとして採用しているpumaでも議論が行われるがまだ絶賛その最中である

https://github.com/puma/puma/issues/2697

そういった背景もあり、このhttp/2化に対する簡易的な返答は前段にnginxやh2oたてるかcloudflare的なものをかますというものだった

ミドルウェアを自前で前段に立てるのはちょっと複雑性があがるので正直面倒だがcloudflareを挟むのはかなり良い感じでワークしてくれるのでDHHもこういうケースの質問にはこれまでそうすりゃいいよ的なレスをしていた

とはいえRailsが自動生成するDockerfileとKamal設定(これもrails8のマイルストーンに入っている)だけでシンプルにやりたい勢向けにHTTP/2、パブリックキャッシュ、X-Sendfileセットアップを提供する設定不要の小さなGoベースのプロキシを構築したよということらしい

多分流れ的にはkamalに組み込まれて利用者は意識することなくその恩恵を教授できるようになると思われる

https://github.com/rails/rails/issues/50479

マイルストーン発表時はAirlock(仮)という名前だったがこの度Thrusterという名前に決定したらしい

https://x.com/dhh/status/1748445755860558174?s=20

私もシンプルRails教の1人なので楽しみである
Go言語でSlackのOAuth2認証を使ってみた

## はじめに

前回は魔法のスプレッドシートの更新通知を流してくれるSlackAppを作成しました。

https://qiita.com/piny940/items/2f0126ad5d71775b3628

一般にSlack Appを公開するにはOAuth2.0認証をできるようにする必要があります。今回はGo言語でOAuth認証のためのサーバーを(サクッと？)立てる方法を解説していこうと思います。

完成したコードはGithub上で公開しているので参考程度にどうぞ。

https://github.com/piny940/magic-spreadsheet-notifier

## 環境

Golang 1.21.1  
labstack/echo 4.11.4  
slack-go/slack 0.12.3

今回はechoを用いてサーバーを立てましたが、Ginとかを使っても大体同じようなコードになると思うので適宜読み替えてください。

## 手順

基本的に[ドキュメント](https://api.slack.com/authentication/oauth-v2)に従い実装していきます。

### 1. 必要な環境変数を設定
SlackAppの管理画面から
- Client ID
- Client Secret

の2つを取ってきます。

取ってきた値は`.env`などに保存してGithub等にpushされないようにしておきましょう。

開発時には`.env`から環境変数を読み取れるよう[godotenv](https://github.com/joho/godotenv)を使用します。

```go
if err := godotenv.Load(".env"); err != nil {
  panic(err)
}
```

### 2. 「SlackAppを追加」ボタンを配置

aタグを配置し、リンクを次のようにします。

```typescript
`https://slack.com/oauth/v2/authorize?scope=${求める権限}&client_id=${Client IDの値}&redirect_uri=${Redirect URL}`
```
求める権限は、`channels:read`や`chat:write`などです。  
Redirect URLは、ユーザーがBotの認証を許可した場合に帰ってくるページのURLを指定します。SlackApp管理画面のRedirectURLに指定したURLの中の1つである必要があります。

ngrokなどで開発サーバーを公開しておくと開発環境でも実験が簡単にできて便利です。

### 3. コールバックのエンドポイントを作成
ユーザーがBotの認証を許可すると、RedirectURLにリダイレクトされます。このときクエリパラメータに`code`が記述されているため、これを取り出します。

```go
code := context.QueryParam("code")
```
これを用いて`oauth.v2.access`APIを叩きます。

```go
response, err := slack.GetOAuthV2ResponseContext(
  c.Request().Context(),
  new(http.Client),
  os.Getenv("SLACK_CLIENT_ID"),
  os.Getenv("SLACK_CLIENT_SECRET"),
  code,
  redirectTo,
)
```

これで上手く行くとレスポンスにはユーザーが属しているワークスペースの`AccessToken`が返ってくるので、これ以降はそのアクセストークンを用いてSlackAPIを叩くことが出来ます。

## 最後に

今回はGo言語でSlackのOAuth2.0認証をできるようにしました。OAuth認証はTwitterに対してPythonで一度実装をしたことがあったため、概念の把握はすぐにできたのですが、Go特有の「HTMLに変数を埋め込むのってどうやるんだろう」みたいなので若干苦戦しました(まぁドキュメント読めば分かる話ですが)。  
次回は、今回書くのをサボったテストを書いていこうと思います。

## 参考資料

https://api.slack.com/authentication/oauth-v2

https://echo.labstack.com

https://github.com/slack-go/slack
OCIRログインの地味な罠

# OCIRにログインできない

Oracle Cloud Infrastructure Registryの認証周りで地味にハマったポイントをまとめてきます。
地味にdockerやbase64の知識を問われる部分です。

# エンドポイント分からない問題
ここのURLを参考にする。
大阪なら`kix.ocir.io`となる。
https://docs.oracle.com/ja-jp/iaas/Content/Registry/Concepts/registryprerequisites.htm

#### 余談
なんでkixなんだろうって思っていたのですが、関西国際空港のコードがkixらしいです。
パプアニューギニアにkiaを取られたらしい。

# ポリシー設定
ココを参考に良しなに。
https://docs.oracle.com/ja-jp/iaas/Content/Registry/Concepts/registrypolicyrepoaccess.htm

ポリシー上、権限のないユーザーでも`docker login`出来てしまうのはOCIの仕様。
（イメージのPullやPushはちゃんとにポリシー準拠になる）

# docker loginできない問題
ここにやり方は書かれていますが、地味な罠があります。
https://docs.oracle.com/ja-jp/iaas/Content/Registry/Tasks/registrypushingimagesusingthedockercli.htm

```
docker login kix.ocir.io
ユーザー名： <tenancy-namespace>/<domain>/<username> ←別ドメインのユーザーの場合はドメインが必要
パスワード: 生成したトークン
```

# Kubernetesでイメージプルできない問題
シークレットの作成方法が地味に罠。

https://docs.oracle.com/ja-jp/iaas/Content/Registry/Tasks/registrypullingimagesfromocir.htm

> 多くの場合、認証トークンを一重引用符で囲みます。それ以外の場合、単一引用符は必要ありません。

このやり方をすると、特殊文字が文字コード（UNICODE?）に変換されるので、パスワード不一致でログインできなくなる。
（なんでそうなるかは知らんがハマった箇所。検証した環境の言語設定が悪さしているかも）

```
kubectl create secret docker-registry <secret-name> --docker-server=<region-key>.ocir.io --docker-username=<tenancy-namespace>/<oci-username> --docker-password='<oci-auth-token>' --docker-email=<email-address>
```
Linux上の`base64`コマンドで作っていくと安全。

authの値を生成する。
**`echo -n`で改行させないことがポイント
（改行入れてしまうのはbase64変換でよくやるミス）**
```
echo -n "<tenancy-namespace>/<domain>/<username>:<token>" | base64 -w 0
```
authはさっき確認した値を入れる。
シングルクォーテーションで囲む。
```
echo -n '{"auths":{"kix.ocir.io":{"username":"<tenancy-namespace>/<domain>/<username>","password":"token","email":"hoge@example.com","auth":"xxx"}}}' | base64 -w 0
```
Secretの一例としてこんな感じになる。
```
apiVersion: v1
kind: Secret
metadata:
  name: myregistrykey
  namespace: awesomeapps
data:
  .dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
type: kubernetes.io/dockerconfigjson
```

#### 参考
https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials
JenkinsのPipelineからシェルスクリプトを呼び出して、戻り値を受け取る

### returnStdout: trueでechoされた文字列を受け取れる
```shell:result.sh
echo "hello"
```
```groovy:pipeline
node{
    def RESULT
    stage('test'){
        RESULT = sh (
             script: "./result.sh",
             returnStdout: true
            )
    }

    stage('check'){
        echo "${RESULT}"
    }
}
```
```groovy:実行結果
hello
```

### おまけ
#### 複数echoしてみる
```shell:result.sh
echo "hello"
echo "world"
```
```groovy:実行結果
hello
world
```
Bright Solutions: Tips for Energy-Efficient Grow Light Systems

![GrowingRack1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3533935/65bd2b76-6ce3-cf4a-a29d-9d92bb4800e8.jpeg)

In the realm of indoor gardening, energy-efficient [grow lights](https://www.growhigher.com/static-racking/) system offer a sustainable and cost-effective solution for cultivating thriving plants while minimizing environmental impact. One key tip for optimizing energy efficiency is to choose LED (Light Emitting Diode) grow lights, which consume significantly less power than traditional lighting systems while producing minimal heat. LED grow lights not only reduce electricity bills but also have a longer lifespan, resulting in fewer replacements and lower maintenance costs over time.

Another tip for maximizing energy efficiency is to properly position and angle grow lights to ensure optimal light distribution across the entire growing area. By strategically placing grow lights closer to plants and adjusting their angle, you can minimize light wastage and ensure that each plant receives adequate illumination for photosynthesis. Additionally, utilizing reflective surfaces such as Mylar or white walls in your grow room can help bounce light back onto plants, further enhancing energy efficiency and maximizing the effectiveness of your grow light system.

Incorporating timers or dimmers into your grow light setup allows you to control the duration and intensity of light exposure, ensuring that plants receive the right amount of light at the right time. By programming grow lights to operate only when needed and adjusting their intensity based on plant growth stages, you can further reduce energy consumption and create a more sustainable indoor gardening environment. With these bright solutions for energy-efficient grow light systems, indoor gardeners can cultivate healthy and thriving plants while minimizing their carbon footprint and energy costs.
Wiresharkで定時に一定時間パケットキャプチャー

# やりたいこと

例えば…

1. 毎日AM09:00になったらWiresharkを自動起動。
1. その後、15分間パケットキャプチャ―したらWireshark終了。

# 処理概要

『引数を渡してWiresharkを起動させて、一定時間後にWiresharkを終了させる』というbatを用意。
上記のbatをタスクスケジューラーで起動。

# bat

```bat:
rem Wiresharkで900秒=15分パケットキャプチャー。
start "" "C:\Program Files\Wireshark\Wireshark.exe" -i 4 -a duration:900 -w %~dp0%date:~0,4%%date:~5,2%%date:~8,2%_%COMPUTERNAME%.pcapng -k


rem Wiresharkの画面が残らないようにキャプチャー終了を見計らってタスク終了。
rem Wireshark起動からパケットキャプチャー開始までのラグを考慮してキャプチャー時間より少しプラスして待つ。
timeout /t 1200 /nobreak >nul
taskkill /im Wireshark.exe /f
```

## Wireshark起動時の引数

### -i 4

**どのインターフェースのパケットをキャプチャーするか** を指定しています。
```4``` はインターフェース番号なので、適宜読み替えて下さい。

インターフェース番号を調べるには、コマンドプロンプトでまず下記。

```bat:コマンドプロンプトをUTF-8に切替
CHCP 65001
```

文字コードを切り替えたら下記。

```bat:Wiresharkのインターフェース番号確認
"C:\Program Files\Wireshark\Wireshark.exe" -D
```

### -a duration:900

900秒パケットキャプチャ―したら **パケットキャプチャ―を終了** します。
あくまでキャプチャーが終了するだけで、Wiresharkの画面は残ります。

### -w %~dp0%date:~0,4%%date:~5,2%%date:~8,2%_%COMPUTERNAME%.pcapng

キャプチャー結果をbatファイルと同じフォルダーに保存。
ファイル名は『yyyyMMdd_PC名.pcapng』という設定です。

### -k

Wireshark起動と同時にキャプチャー開始。

# タスクスケジューラー

『スリープや定時再起動によって実行されなかった』といったことが無いように注意。


# 参考サイトさん

https://mistymagich.wordpress.com/2014/07/14/windows%E3%81%A7%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%AB%E3%82%88%E3%82%8Bwireshark%E3%81%AE%E8%B5%B7%E5%8B%95/

https://www.javadrive.jp/command/display/index5.html

# バージョン

Microsoft Windows [Version 10.0.19045.3996]
Wireshark 4.2.2
ASHのデータを取り出す（usernameをサブクエリで）

以下のSQLを実行する。

select 
SAMPLE_TIME,
inst_id,
(select username from dba_users du where du.user_id = vs.user_id) as USERNAME,
SESSION_ID,
SESSION_SERIAL#,
QC_INSTANCE_ID,
QC_SESSION_ID,
SQL_ID,
SQL_OPNAME,
SQL_PLAN_HASH_VALUE,
SQL_EXEC_ID,
SQL_EXEC_START,
EVENT,
WAIT_CLASS,
PROGRAM,
MODULE,
MACHINE,
PGA_ALLOCATED,
TEMP_SPACE_ALLOCATED
from gv$active_session_history vs
--from dba_hist_active_sess_history vs
where SAMPLE_TIME
between to_date('2019-01-05 20:00:00','YYYY-MM-DD HH24:MI:SS') -- 開始時間
and to_date('2019-01-05 20:10:00','YYYY-MM-DD HH24:MI:SS') -- 終了時間
-- > sysdate - 3/24
order by sample_time
実行計画をAWRのスナップショットから取り出す

実行計画をAWRのスナップショットから取り出すには
以下のSQLを実行する。

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('<SQL ID>'))

例）
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('1uptnptv0qg27'))


SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR(sql_id=>'3131810679', format=>'TYPICAL'));
fishでRustをインストールする

# インストール
途中でオプションを聞かれるが、とりあえず1を選べばいいと思う。
```fish
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
...
Current installation options:


   default host triple: x86_64-apple-darwin
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1
...
To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source "$HOME/.cargo/env"
```

# PATHを通す
```fish
$ source "$HOME/.cargo/env"
~/.cargo/env (line 4): 'case' builtin not inside of switch block
case ":${PATH}:" in
^~~^
from sourcing file ~/.cargo/env
source: Error while reading file '/Users/kotaro/.cargo/env'
```
素直に`source "$HOME/.cargo/env"`を実行するとエラーになるが要はPATHさえ通れば良いので、`fish_add_path`を実行すれば良い。
```fish
$ fish_add_path $HOME/.cargo/bin
$ rustc -V
rustc 1.75.0 (82e1608df 2023-12-21)
```

# 参考
https://doc.rust-lang.org/book/ch01-01-installation.html
https://github.com/rust-lang/rustup/issues/478#issuecomment-1288194464
SQLHealthCheckについて

まず、SQLの状況を調査する方法・ツールなど

1)EXPLAIN PLAN
2)SQL*PlusのAUTOTRACE
3)V$SQL_PLANおよび関連ビュー
4)SQLトレース/tkprof
5)Oracle Enterprise Manager Diagnostics Pack / Tuning Pack
6)イベント 10046/10053 トレース

・SQLHCの使い方

任意のディレクトリに、以下4つのSQLを配置

・sqlhc_sta.sql
・util_planx.sql
・sqldx.sql
・sqlhc.sql

sqlplusで、SYSとしてログイン
以下のSQLを実行

SQL> START 　　sqlhc.sql 　　[T|D|N] 　　SQL_ID

コマンドラインで、TかDかNを選ぶ箇所がある
・TuningPackとDiagnosticPackの両方があるなら　T
・DiagnosticPackだけあるなら　D
・どっちもないなら　N

参照WEBページ
https://www.intellilink.co.jp/column/oracle/2015/021600.aspx
りあクト！【① 言語・環境編】を読もう

# はじめに
こんにちは！
昨今のフロントエンド関連、ありえん難しいです。
わたしは業務でReactやvue以降のモダンフロントエンド技術に触れたことがなく、
ずっと、バックエンドFWとHTML/CSS, JQueryという古の技術を使っていました。
エンジニアとして四年目を迎えているのにも関わらずこれは非常にマズいと思い、Reactやそのフレームワークを独学していますが、
- え...なんでこの書き方にする必要があるの...?
- なんとなくこう書いているけどこれは合っているの?
- なにをやっているか理解できずお作法として書いているコードがある

などなど、不安な気持ちでいっぱいです。

そんな中、モダンフロントエンド周りを理解するために購入した一冊の本がとてもためになったので、紹介します。

# りあクト！ TypeScriptで始めるつらくないReact開発 第4版【① 言語・環境編】
大岡由佳さんという方が出している本です。

購入はこちらから
[りあクト！ TypeScriptで始めるつらくないReact開発 第4版【① 言語・環境編】](https://booth.pm/ja/items/2368045)
試し読みも可能なうえ、試し読みの時点で素晴らしさを感じました。

読んでみて感じたことをザっと書いていきますが、
【おすすめできる人】が特に重要だと感じました。

- **全体的な所感**
    - 会話形式で読みやすい（200ページ強あったけどささっと読めた）
    - 会話形式であることによって、読み手が疑問に思うことを会話の中で解決してくれることが多い
    - javascriptの「言語」的な部分を解説してくれている
    - 登場人物の秋谷さんのバックボーンが自分と近く、同じような（？）気持ちになれる
<br>

- **これを読むと何がうれしいか**
    - 今まで何となく書いていた記法の詳細（functionキーワードでの関数とアロー関数の違いとか）をきちんと理解できる
    - 関数型プログラミングの良さを理解できる
    - TypeScriptのすばらしさを理解できる
    - Udemyの動画や、とりあえず物作りながら覚える系の本で「え、これやっていいの...?」と思ったことを払拭できる（例えば、型アサーションを普通に使う教材とかで「え...?」って思ってました。）
    - 各技術について使い方だけでなく、その登場した背景や歴史を知ることができる

<br>

- おすすめできる人
購入ページ等にも記載がありますが、わたしの所感も交えて書きます。
    - バックエンド寄りのバックボーンからモダンフロントエンドに手を出そうとしている人
    - React独学で不安な人
    - **細かいことが気になる人**
    - その他、購入ページの[【こんな方にオススメ！】](https://booth.pm/ja/items/2368045)に該当する人

# おわりに
この本を読んでいて、内容のすばらしさに関心すると同時に、「学習教材の選定って、すごく難しいな」と感じました。
自分のバックボーンと教材が提供してくれる内容がマッチしているかってすごく重要なので、完全初心者でも、そうでない人も、新しい技術を学ぶ際の入門本は慎重に選んだほうがいいなと感じました。
【初心者向け】Shopifyの会員登録画面に利用規約の同意を追加する方法

# はじめに
Shopifyで利用規約の同意を追加する方法について調査するとカードページに追加する方法が見つかりますが、これでは毎回同意が必要になります。
また、会員登録時に利用規約への同意を得るShopifyアプリもありますが、これは毎月の使用料がかかります。
そこで、Shopifyアプリを使用せず会員登録時に利用規約への同意を得る方法を調査しました。
以下にその手順を示します。
ご参考になれば幸いです。

なお、本手順ではShopifyのテーマとしてDawn Ver13.0.0を使用しています。

# こんな人に読んでほしい
* ShopifyでECサイトを始めたい方
* ShopifyでECサイトを運営中の方
* Shopify開発言語(Liquid)を学びたい方

# 前提条件
* パソコン上にShopify管理画面へのアクセス環境が整っていること

# 利用規約を作成する
最初に利用規約を作成します。
管理画面から「設定」－「ポリシー」を選択し、「利用規約」を入力して保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/b2fe0fa1-55f6-8217-2581-2a405bf9d8c2.png)

作成した利用規約のURLは下記になります。
/policies/terms-of-service

# 会員登録ページを編集する
変更前の会員登録ページは下記です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/fec7819e-a4e5-6548-da7a-04707500eb3e.png)
パスワードと「作成する」ボタンの間に利用規約の同意チェックボックスを追加します。
また利用規約ページへのリンクも追加します。

1. 管理画面から「オンラインストア」－「テーマ」－「・・・」－「コードを編集」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/9c6717c2-be6a-8e9c-b45e-21ded97e7c6c.png)
　

2. 検索バーに「main-register」を入力し、「main-register.liquid」ファイルを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/bae92c35-79de-df64-864e-833b8b064c4f.png)
　

3. 「main-register.liquid」ファイルを編集(左が変更前、右が変更後)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/24487183-623b-0047-9d25-f35794fd5a58.png)

＜コード説明＞

利用規約の同意チェックボックスと利用規約ページへのリンクを追加しています。
```
     <div>
        <input type="checkbox" id="optin" onclick="changeDisabled();" autocomplete="off" required="required" >
            <label for="optin">
                <a href="/policies/terms-of-service" target="_blank">利用規約</a>
                に同意します
            </label>
    </div>

```
　
「作成する」ボタンタグにidを付加し、初期状態を選択不可にしています。
```
    <!--<button>-->
    <button id="registration_btn" disabled>
```
　
利用規約の同意チェックボックスの状態に応じて「作成する」ボタンの選択可否を制御するJavaScriptを追加しています。
```
<script>
  function changeDisabled(){
      optin = document.getElementById('optin');
      if(optin.checked) {
          document.getElementById('registration_btn').disabled = "";
      } else {
          document.getElementById('registration_btn').disabled = "disabled";
      }
  }
</script>
```
　
これにより、利用規約の同意チェックボックスがONの時のみ「作成する」ボタンが選択できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659381/d13aae9a-c8ec-3def-397c-ccab5ab22f45.png)

# 最後に
いかがでしたでしょうか。
Shopifyアプリを使用せずに手軽に実装できましたね。
これを機に、Shopifyのコード編集に挑戦してみてください。
MySQLでロックが発生した時に調べたことの備忘録

# ロックするときの動作
1. スキャンされた行のロックを取得
1. 結果セットに含まれない行のロックを解除

# ロックが発生したときに確認すること
- インデックスが使用されずに全検索されていないか
- 想定した結合順序で実行されているか
- ギャップロックで想定外のレコードがロックされていないか

## 対処方法
 - インデックスが使用されていなければ追加する
 - ヒント句を使用して結合順序や使用するインデックスを固定する
 - INNER JOINの変わりにSTRAIGHT_JOINを使用して結合の順序を固定する

# MySQLのギャップロックについて
- インデックスが張られている項目の、存在しない行に対してロックが発生する


| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| o | x | x | x | o | x | x | o |

- 上記レコードの3又は1～5に対してselect for updateをすると、存在しない2~4に対してロックがかかる
- 上記レコードのに4～6に対してselect for updateをすると、存在しない2~4と6～7に対してロックがかかる

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| x | x | x | x | o | x | x | x |

- 上記レコードのに4～6に対してselect for updateをすると、存在しない4より前と6以降のレコードに対してロックがかかる

# 参考サイト
https://dev.mysql.com/doc/refman/8.0/ja/innodb-locks-set.html

https://dev.mysql.com/doc/refman/8.0/ja/innodb-locking.html#innodb-gap-locks
BITNAVI(ビットナビ)｜仮想通貨の海外総合サイト

BITNAVI(ビットナビ)は、海外の仮想通貨情報サイトです。

仮想通貨FX歴5年以上のトレーダーが、海外で話題の仮想通貨や、おすすめの仮想通貨FX取引所を紹介します。

BybitやFXGT、XMTradingなどの海外FX業者についても紹介しています。

また、仮想通貨関連のサービスも展開しているので、ぜひ気になるサービスがあればご利用ください。

[BITNAVI(ビットナビ) ｜仮想通貨の海外総合サイト](https://crypto-media.jp/)

[CoinBot(コインボット) | 海外仮想通貨ニュースサイト](https://news.crypto-media.jp/)

[クリプトチェッカー | 仮想通貨の価格がチェックできるサイト](https://checker.crypto-media.jp/)

[ビット掲示板 | 仮想通貨コミュニティ掲示板サイト](https://community.crypto-media.jp/)
ISO時刻基準変換例

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;

public class DateTimeConversionTest {

    public static void main(String[] args) {
        List<String> sampleIsoDateTimes = getIsoDateTimeSamples();

        for (String inputIsoDateTime : sampleIsoDateTimes) {
            try {
                String formattedDateTime = convertToCustomFormat(inputIsoDateTime);
                System.out.println(inputIsoDateTime + " => " + formattedDateTime + " 変換されました");
            } catch (DateTimeParseException e) {
                System.out.println(inputIsoDateTime + " このサンプルは変換されませんでした");
            }
        }
    }

    private static List<String> getIsoDateTimeSamples() {
        return Arrays.asList(
            "2021-11-11",
            "2021-11-11T13:23",
            "2021-11-11T13:23:45",
            "2021-11-11T13:23:45.123",
            "2021-11-11T13:23:45Z",
            "2021-11-11T13:23:45+09:00",
            "2021-11-11T13:23:45-09:00",
            "2021-11-11T13:23:45.123+09:00",
            "2021-11-11T13:23:45.123-09:00"
        );
    }

    private static String convertToCustomFormat(String inputIsoDateTime) {
        try {
            ZonedDateTime dateTime;
            try {
                dateTime = ZonedDateTime.parse(inputIsoDateTime, DateTimeFormatter.ISO_DATE_TIME);
            } catch (DateTimeParseException e) {
                // タイムゾーン情報がない場合はLocalDateTimeにパースしてからZonedDateTimeに変換
                LocalDateTime localDateTime = LocalDateTime.parse(inputIsoDateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                dateTime = ZonedDateTime.of(localDateTime, ZoneId.of("Asia/Tokyo"));
            }
            // カスタムフォーマットを使用して出力
            DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss,SSSSSSXXX");
            return dateTime.format(customFormatter);
        } catch (DateTimeParseException e) {
            // 解析に失敗した場合
            throw new DateTimeParseException("Failed to parse date time: " + inputIsoDateTime, inputIsoDateTime, 0);
        }
    }
}
シェルスクリプトからPythonを呼び出して戻り値を受け取る

## シェルスクリプトからPythonを呼び出して戻り値を受け取りたい
#### sys.stdout.write()で戻り値として受け取れる
```Python:result.py
import sys
sys.stdout.write("hoge")
```
```shell:call.sh
result=$(python result.py)
echo $result
```
```shell:実行結果
hoge
```

### おまけ
複数sys.stdout.write()を書くと
```Python:result.py
import sys
sys.stdout.write("hoge")
sys.stdout.write("fuga")
sys.stdout.write("piyo")
```
全部返ってくるので、欲しい値だけ返そう
```shell:実行結果
hogefugapiyo
```
3次元物理シミュレーションのサロゲートモデルを作りたい

## はじめに
[前回のデータ変換の記事](https://qiita.com/rm0063vpedc15/items/70b5719353cbe4bf22b8)の続きとなります。今回の記事では、実際に3次元物理シミュレーションのサロゲートモデルを作っていきます。

## 1. 利用するデータセット
今回はパブリックに公開されている3次元物理シミュレーションのデータを活用させてもらいます。ジェットエンジンのブラケットについてのシミュレーションとなっています。

[SimJEB: Simulated Jet Engine Bracket Dataset](https://simjeb.github.io/)

データ変換については前回の記事を参照ください。

[3次元物理シミュレーションのサロゲートモデルを作りたい(データ変換編)](https://qiita.com/rm0063vpedc15/items/70b5719353cbe4bf22b8)

## 2. 機械学習の入出力
実際に機械学習の入出力となるのは以下の内容になります。
今回は所有しているGPU性能等の関係上、1出力にしています。

```
x [ノード数, 属性数(7)]: 機械学習の入力となる側
    x axis: float, 
    y axis: flaot, 
    z axis: float, 
    condition of constraint: 0(off) or 1(on),
    force magnitude x(vertical): float,
    force magnitude y(vertical): float, 
    force magnitude z(vertical): float

y [ノード数, シミュレーション結果数(1)]: 機械学習の出力側、シミュレーションの結果
    magnitude of displacement "sqrt(x^2+y^2+z^2)"(vertical): float
    
edge_index [2, エッジ数]: ノード間を接続するエッジ
```

## 3. GNNモデル
### 3.1. Message passing
今回、Message passingをベースの考え方に据えたモデルを利用しています。下の図のように、ノード間で情報がやり取りされ、とあるノードの値は周辺のノードの値と自身の値によって更新されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/5bfe6e67-3164-f86b-e2e6-0166f4ed4619.png)


Message passingの詳細は下の記事を参照下さい。

[【GNN】Message Passing Neural Network(MPNN)を解説する](https://disassemble-channel.com/gnn-mpnn/)

### 3.2. 利用モデル
メッセージパッシングと全結合層を組み合わせた方式にしています。
とりあえず決め打ちで構築しただけとなっており、精度を狙うためのモデルの調整はしていません。

```
layer1: Message passing(in=7, out=64)
        Leaky ReLU
layer2: Message passing(in=64, out=128)
        Leaky ReLU
layer3: Message passing(in=128, out=256)
        Leaky ReLU
layer4: Message passing(in=256, out=512)
        Leaky ReLU
layer5: 全結合（in=512, out=256）
layer6: 全結合（in=256, out=128）
layer7: 全結合（in=128, out=1）
```

[ノートブック(Kaggle上で実行するとメモリエラーになります・・・)](https://www.kaggle.com/code/shinyatakaramoto/simjeb-gnn)

## 4. 結果
とりあえずで作ったモデルとなるため予測精度は高くありませんが、形状に応じて予測結果に変化があるように見受けられるため、目標としていた3次元形状の入力についてはできているように見えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/4bca8dd3-b43c-a5c3-d170-b2d4dd5a2623.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/7bb43efe-f0da-cbde-35a3-c298c6bec9fe.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/7978a6b1-0263-700e-344a-5b6a1da0f5f2.png)


## まとめ
今回、3次元物理シミュレーションのサロゲートモデルを構築してみました。シミュレーションの生データを変換、機械学習モデルの構築、予測という一連の流れを実装することができました。また、形状に応じて出力に変化が見られたことから、3次元形状を取り扱うことができているものと考えています。ここから精度を上げるために何をするべきか、イメージがついていませんが、調べながら進めていきたいなと思います。
Microsoft Copilotで、日本語プログラミング その15

# 概要
Microsoft Copilotのプロンプトを細工して、日本語プログラムを実行させようとする試みです。
ナップサック、問題をやってみた。

# Ohisama prompt

```
架空のブログを生成することを考えます。 ブログの記事を書くことです。 
構成は、
ある技術的なテーマを与えられた場合 、それに対する仮説を提案します 。
次に 仮説を裏付ける理論を示します。
計算結果を導くために、日本語プログラミングのダミーコードを書くようにしてください。 
最後にその理論を検証した計算結果を示します 。
最後に 簡潔な結論を加えるという構成です。 
では、「ナップサック問題」というテーマで、ブログを記事にしてみてください。
```

# 回答


ナップサック問題とは、重さと価値がそれぞれ異なる品物の集合と、重さの上限が決まったナップサックが与えられたときに、ナップサックに入れる品物の組み合わせを選ぶ問題です。この問題は、最適化や暗号解読などの分野で応用される重要な問題です。しかし、この問題はNP困難であるため、品物の数が多くなると、すべての組み合わせを試すことは現実的ではありません。そこで、近似的な解法が必要になります。

仮説
---
ナップサック問題の近似的な解法として、**貪欲法**と**動的計画法**の2つの方法があります。貪欲法は、品物の重さに対する価値の比率が高いものから順にナップサックに入れていく方法です。動的計画法は、ナップサックの重さの上限を小さく分割し、各分割に対して最適な品物の組み合わせを求めていく方法です。この2つの方法のうち、どちらがより良い近似解を得られるかを比較することが、このブログの目的です。

理論
---
貪欲法と動的計画法の理論を簡単に説明します。

貪欲法は、以下のような手順で行われます。

1. 品物の集合を、重さに対する価値の比率が高いものから低いものへと並べ替える。
2. 並べ替えた品物の集合から、先頭の品物を取り出す。
3. 取り出した品物がナップサックに入る場合は、ナップサックに入れる。入らない場合は、捨てる。
4. 品物の集合が空になるか、ナップサックがいっぱいになるまで、2と3を繰り返す。

動的計画法は、以下のような手順で行われます。

1. ナップサックの重さの上限を$W$とする。$W$を$1, 2, \dots, W$という$W$個の分割に分ける。
2. 各分割に対して、以下の操作を行う。
    1. 品物の集合を、重さが分割以下のものだけに絞る。
    2. 絞った品物の集合の部分集合のうち、重さの合計が分割以下で、価値の合計が最大となるものを求める。
    3. 求めた部分集合を、分割に対応するナップサックの解として記録する。
3. $W$個の分割に対応するナップサックの解のうち、価値の合計が最大となるものを、最終的なナップサックの解とする。

ダミーコード
---
日本語プログラミングのダミーコードを以下に示します。このコードは、実際には動作しませんが、アルゴリズムの流れを理解するのに役立ちます。

```python
# 貪欲法のダミーコード
品物の集合を重さに対する価値の比率で並べ替える
ナップサックの重さの上限を設定する
ナップサックの重さの合計を0に初期化する
ナップサックの価値の合計を0に初期化する
ナップサックに入れる品物の集合を空に初期化する
品物の集合が空でない間、繰り返す
    品物の集合から先頭の品物を取り出す
    取り出した品物の重さをナップサックの重さの合計に加える
    もしナップサックの重さの合計がナップサックの重さの上限以下なら
        取り出した品物の価値をナップサックの価値の合計に加える
        取り出した品物をナップサックに入れる品物の集合に追加する
    そうでなければ
        取り出した品物の重さをナップサックの重さの合計から引く
        取り出した品物を捨てる
ナップサックの価値の合計とナップサックに入れる品物の集合を出力する

# 動的計画法のダミーコード
ナップサックの重さの上限を設定する
ナップサックの重さの上限を1からナップサックの重さの上限までの分割に分ける
各分割に対応するナップサックの解を格納する配列を用意する
各分割について、繰り返す
    品物の集合を重さが分割以下のものだけに絞る
    絞った品物の集合の部分集合のうち、重さの合計が分割以下で、価値の合計が最大となるものを求める
    求めた部分集合を配列に格納する
配列の中で、価値の合計が最大となる部分集合を出力する
```

計算結果
---
以下のような品物の集合とナップサックの重さの上限を例として考えます。

| 品物 | 重さ | 価値 |
| --- | --- | --- |
| A | 2 | 3 |
| B | 1 | 2 |
| C | 3 | 4 |
| D | 2 | 2 |
| E | 4 | 5 |

ナップサックの重さの上限は7とします。

貪欲法で求めたナップサックの解は以下のようになります。

| 品物 | 重さ | 価値 | 重さに対する価値の比率 |
| --- | --- | --- | --- |
| B | 1 | 2 | 2 |
| A | 2 | 3 | 1.5 |
| C | 3 | 4 | 1.33 |
| E | 4 | 5 | 1.25 |
| D | 2 | 2 | 1 |

品物B, A, Cをナップサックに入れると、重さの合計が6で上限以下であり、価値の合計が9となります。品物Eを入れようとすると、重さの合計が10で上限を超えるため、入れません。品物Dは重さに対する価値の比率が最も低いため、入れません。したがって、ナップサックの解は{B, A, C}で、価値の合計は9となります。

動的計画法で求めたナップサックの解は以下のようになります。

| 分割 | 品物の集合 | 重さの合計 | 価値の合計 |
| --- | --- | --- | --- |
| 1 | {B} | 1 | 2 |
| 2


以上。
Ruby + Goで魔法のスプレッドシートの更新を通知するSlackAppを作ってみた

## はじめに
IT企業のインターン情報が多数掲載されている[魔法のスプレッドシート](https://magic-spreadsheets.pages.dev)ですが、新しい募集が掲載されても変更の通知を受け取ったり差分を確認する機能がないため、優秀な人材と企業がマッチする機会が損なわれるという課題を感じていました。

そこで今回は、魔法のスプレッドシートが更新されたときにSlackに通知を流すSlackAppを作ってみました。

完成したコードはGithubで公開しているので参考程度にどうぞ。

https://github.com/piny940/magic-spreadsheet-notifier

SlackAppはこちらのサイトから追加できるようにしました。

https://magic-spreadsheet-notifier.piny940.com

## 技術選定

今回作るSlackAppの動作は写真のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330232/ebdba5f4-54b9-0915-1091-8c799fc2eb63.png)


1. 魔法のスプレッドシートのサイト(Notion)を取得
1. 自身の管理するDB上に保存されている求人一覧を取得
1. 1と2の差分を計算
1. DB上に保存している求人の追加・削除
1. 追加された求人についてSlackに通知

技術として検討する必要があるのは、

1. SlackApp本体(一番左の技術)を何の言語で書くか
2. DB(左から3番目)を何で作るか

の2点になります。

1の候補としては、Go, Typescript, Rubyの3つを、  
2の候補としてはPostgres, AWSのDynamodb, FirebaseのFirestoreの3つ  
を考えました。

1についてはNotionからデータを取得できるかという点で大きな制約がありました。

魔法のスプレッドシートは2023年からNotionのwikiで公開されるようになりました。NotionのAPIは、自身が管理するノート以外は(たとえ公開されているノートでも)APIで取得することが出来ないので、スクレイピングでデータを集めることにしました。

しかし、Notionのwikiは動的にレンダリングされているため、単純にHTTP GETでデータを集めることは出来ません。そこで、今まで何度かお世話になったことがある[selenium](https://www.selenium.dev)を使うことにしました。

seleniumはchromeのようなブラウザの動きを模倣してくれるライブラリで、複数の言語でサポートされているという強みがあります。seleniumは今までのプロジェクトでも使ったことがあり、軽く調べた限りではweb driverを使ったスクレイピングにはseleniumがよく使われていそうだったので今回もseleniumを使うことにしました。

seleniumは色々な言語でライブラリが存在しており、Go, Node, Rubyでも対応するライブラリが見つかりました。ただ、Goの[selenium client](https://github.com/tebeka/selenium)は3年以上メンテナンスされていなかったため、この時点でGoは却下されました。  
また、javascriptのseleniumについては、ドキュメントが存在しなかった(自動生成が壊れているらしい https://github.com/SeleniumHQ/selenium?tab=readme-ov-file#documenting )ため、消去法でRubyを採用することにしました。

2については、まず「今回どんなデータを保存する必要があるか」から考えました。今回保存する必要があるのは、求人データのリストに過ぎず、JSONレベルの記述力があれば事足ります。そのためにSQLのER図を考えてcreate tableして・・・というのは面倒だったため、今回はNoSQL系のサービスを使うことにしました。

次にDynamoDBを軽く触ってみたのですが、DynamoDBはデータが整数の場合`{ N: "3" }`、文字列の場合`{ S: "foo" }`というような形で返されるため、いちいちMarshalとUnmarshalをしないという手間がありました(RubyのSDKだと必要ないっぽい？)。これは面倒だと感じ、DynamoDBはやめてFirestoreを使うことにしました。

Firestoreは無料枠が読み込み・書き込みの回数で決められていて、個人開発の範囲で無料枠を超えることはまずありえないため他プロジェクトでも重宝しています。

## 実装
実装の中で最も難しかったのはスクレイピングの部分です。基本的にクラス名を用いてエレメントを取得していくのですが、来年の「魔法のスプレッドシート2024」が出たときにできるだけ変更箇所が少なくなるようにしたいという思いがありました。

そのため、BlockIDなどのクラス名で指定するのではなく、`.notion-table-view-row`のような普遍的なクラス名を駆使してエレメントを指定するように意識しました。

また、来年の魔法のスプレッドシートで万が一Notionが使われなくなったとしても影響が最小限に抑えられるよう、コードを適切に分割し、スクレイピングの処理が他のコードに影響しないよう意識しました。

## SlackAppの公開
SlackAppを公開するにはOAuth2認証をできるようにする必要があります。そのためGo言語で簡易的なサーバーを立てて認証ができるようにしました。これについては別記事で解説しようと思います。

## 最後に
今回はSlackAppを作成して魔法のスプレッドシートの更新通知がSlackへ流れるようにしました。  
しかしまだテストコードが全然書けていないので、これからテストをごりごり書いていこうと思っています。
エンジニアは思った。git mvを使えるようになりたい。

先日東京で雪が積もった際、故郷のことを思い出してテンションが上がってしまいました。

# 今回の話
ーある日の業務中ー
**上司**：「新しいディレクトリを作ってそこに元からあったファイルを入れて欲しいんだよね。」
**私**：「承知しました。」

というわけでファイルの移動をすることになりました。

**私の脳内**：「あのファイルって確かgitに一度pushしたから追跡対象になっているよな…」
「こっちで適当に移動するとpushするとき、エラーになりそうだな」
「それ専用のコマンドってあるんだろうか」

-google先生に聞いてみる

「なんかgit mvっていうコマンドを使うらしいな」


# git mv
git上の追跡対象のファイルの名前変更や移動が行えるコマンドです。
このコマンドを使うことで**追跡を維持できる**というのが重要な点になっています。
以下がコマンドです。

### ファイル名の変更
```terminal
git mv 旧ファイル名　新規ファイル名　
```
### ファイルの移動
```terminal
git mv ファイル名　移動先
```

:::note warn
ここで注意すべきことは、名前変更や移動したいファイルがあるディレクトリに移動してコマンドを実行することです。
:::
ちなみに私はディレクトリに移動するのを忘れていて、コマンド使えないと永遠に怒られてしまい、気づくまでに時間がかかってしまいました。

# 終わりに
新人エンジニアにとってgitと仲良くなるのは難しいことではありますが、
このコマンドを使えるようになって少しでも仲良くなりましょう！

# 参考

https://git-scm.com/docs/git-mv
pnpmを使ってElectronをビルドした時に起動しない問題の修正

# はじめに

Electron＋Next.jsでデスクトップアプリの開発をしています。
解決方法としては簡単だったのですが、長時間バグに悩まされたので記録に残します

# 問題

Electronをビルドした後に、アプリを起動したところ以下のエラーが発生しました。

> Uncaught Exception:
> Error: Cannot find module ‘form-data’
> Require stack:
> - /Applications/XXX.app/Contents/Resources/app.asar/node_modules/axios/dist/node/axios.cjs
> - /Applications/XXX.app/Contents/Resources/app.asar/main/index.js
> -
> at node:internal/modules/cjs/loader:1084:15
> at Function._resolveFilename (node:electron/js2c/browser_init:2:117419)
> at node:internal/modules/cjs/loader:929:27
> at Function._load (node:electron/js2c/asar_bundle:2:13377)
> at Module.require (node:internal/modules/cjs/loader:1150:19)
> at require (node:internal/modules/cjs/helpers:110:18)
> at Object.<anonymous> > (/Applications/XXX.app/Contents/Resources/app.asar/node_modules/axios/dist/node/axios.cjs:4:20)
> at Module._compile (node:internal/modules/cjs/loader:1271:14)
> at Object..js (node:internal/modules/cjs/loader:1326:10)
> at Module.load (node:internal/modules/cjs/loader:1126:32)

![electron-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413529/6d075769-682c-7075-a5d4-b2717074d5ef.png)


# 解決方法

問題は `pnpm` にありました。そして、ちゃんとドキュメントにも記載されていました。

https://github.com/electron-userland/electron-builder?tab=readme-ov-file#note-for-pnpm

`electron-builder` かつ `pnpm` を使っている場合、以下を `.npmrc` に記載する必要があります。

```
node-linker=hoisted
public-hoist-pattern=*
shamefully-hoist=true
```

`.npmrc` の場所は[こちら](https://pnpm.io/ja/npmrc)を参考にしてください。
同じチームメンバーも同じ問題にハマってしまうので、`pnpm-workspace.yaml` に記載のフォルダの配下に書くと良いかもしれないですね。

> pnpm config コマンドを使用して、ユーザーおよびグローバルの .npmrc ファイルの内容を更新および編集することができます。
> 
> 関連する4つのファイルは次のとおりです。
> 
> - プロジェクトごとの設定ファイル(/path/to/my/project/.npmrc)
> - ワークスペースごとの設定ファイル (pnpm-workspace.yaml ファイルが含まれているディレクトリー)
> - ユーザーごとの設定ファイル(~/.npmrc)
> - グローバルな設定ファイル (/etc/npmrc)

上記を設定し再度実行すればエラーは消えているはずです！

# おわりに

英語読む拒絶反応から半日以上時間を溶かしてしまいました。ドキュメントは公式のをしっかり読みましょう(自戒)

# 参考

- https://github.com/electron-userland/electron-builder?tab=readme-ov-file#note-for-pnpm