OmniAICreator/Qiita-1.07M huggingface dataset:
Laravel10でCRUDのパラメータを数字に限定する方法

## 何が問題か

管理画面で数字パターンの時は詳細ページを表示、特定の文字列の時は別のページを表示、ということを実現したかったのですが、 `Route::resouce` で CRUDのルーティングを一括定義すると、 `/reports/{report} : ReportsController@show` のように定義されるため、`{report}` パラメータにどのような値がきても `show` メソッドが実行されることになります。
そのため、`{report}` パラメータを数字に限定する必要がありました。

## 実装

```php:routes/web.php

Route::pattern('id', '[0-9]+');

# 要ログインページ(管理画面 下層ページ)
Route::middleware('login')->group(function(){
    # レポート管理 CRUD
    Route::resource('reports', ReportsController::class)->parameters(['reports' => 'id']);

    # レポート管理 その他のアクション
    Route::group(['prefix' => 'reports', 'as' => 'reports.'], function(){
        // 日別レポート
        Route::get('daily', [ReportsController::class, 'daily'])->name('daily');
    });
});
```

`parameters`メソッドで、ダイナミックパラメータ `reports ({report} に相当)` を `id ({id} に相当)` に置き換えます。
さらに、 `id` パラメータを `Route::pattern` で数字に限定することで、 `/reports/1` にアクセスした場合は `ReportsController@show` メソッドを、 `/reports/daily` にアクセスした場合は `ReportsController@daily` メソッドを使用する、ということが可能になります。

## 動作確認

parametersメソッド設定前
`Route::resource('reports', ReportsController::class);`

```
php artisan route:list

  GET|HEAD        reports ............................ reports.index › ReportsController@index
  POST            reports ............................ reports.store › ReportsController@store
  GET|HEAD        reports/create ................... reports.create › ReportsController@create
  GET|HEAD        reports/daily ...................... reports.daily › ReportsController@daily
  GET|HEAD        reports/{report} ..................... reports.show › ReportsController@show
  PUT|PATCH       reports/{report} ................. reports.update › ReportsController@update
  DELETE          reports/{report} ............... reports.destroy › ReportsController@destroy
  GET|HEAD        reports/{report}/edit ................ reports.edit › ReportsController@edit
```

parametersメソッド設定後
`Route::resource('reports', ReportsController::class)->parameters(['reports' => 'id']);`

```
php artisan route:list

  GET|HEAD        reports ............................ reports.index › ReportsController@index
  POST            reports ............................ reports.store › ReportsController@store
  GET|HEAD        reports/create ................... reports.create › ReportsController@create
  GET|HEAD        reports/daily ...................... reports.daily › ReportsController@daily
  GET|HEAD        reports/{id} ......................... reports.show › ReportsController@show
  PUT|PATCH       reports/{id} ..................... reports.update › ReportsController@update
  DELETE          reports/{id} ................... reports.destroy › ReportsController@destroy
  GET|HEAD        reports/{id}/edit .................... reports.edit › ReportsController@edit
```

公式ドキュメントに `parameters` メソッドの記述が見当たりませんでしたが、
5系開発時の経験からヤマ勘でやってみたら、うまくいったので投稿しました。

## 備考 parametersメソッドについて

ルートパラメータ名を上書きするメソッドです。

ルートではなく、コントローラーのほうにこのオプションの説明がありました。
(「リソースルートパラメータの命名」の項を参照)

https://readouble.com/laravel/10.x/ja/controllers.html#restful-naming-resource-route-parameters

もっと良い方法があったら是非コメントをお願いします。
完全未経験からwebエンジニアになって1ヶ月が経ちました

webエンジニアになってから1ヶ月が経ちました。
これまでの所感などを書きます。私の自己紹介については下記の記事をご覧ください。
https://qiita.com/aliveIke/items/f947d316d3a4300dc65f

## 概要
- どのようなことをしたか
- どう感じたか
- 今後に向けて

## どのようなことをしたか

仕事の経過を記述します。

### ０週目　打ち合わせ

今やっている案件の打ち合わせをして、自分がどのように仕事を進めていくのか決めました。
まずはモデルとなるwebサービスがあるので、そのデザインや機能をひたすらコピーすることからはじめることになりました。

この時点で社長から「10月までに普通のエンジニアの数年分くらいの仕事をするから」と言われ、不安も感じましたが、「やってやるぞ！」と奮い立ちました。

### １〜２週目　フロントエンドの業務　

まずは必要なページをリストアップしました。案件のモデルとなるwebサービスがあったので、そこからビューを引っ張ってきました。

驚いたこととしては、参考にしたwebサービスをみると、CSSで要素を並べたいときに自分が多用してた```float```をほとんど使ってなかったことです。ほとんど```flex```が```grid```を使いました。使ってみると同じスタイルをつくるにしても```flex```の方が親要素に一回書けばいいだけなので楽だなあと思いました。

今この業務を振り返ってみると、画像やアイコンも同じものでコピーしようとしていましたが、どうせ本番になったら同じものを使うはずがないので、あまり必要ない業務もしていたなと感じます。

### ３〜４週目　バックエンドの業務

ブラウザで表示されている内容から必要なデータを洗い出してデータベースをつくりました。

またメッセージ送信や検索機能など、必要な機能をLaravelを使って実装していきました。ビューページで動的に変更したいところはAlpinejsを主につかっていました。

バックエンド実装のときに**ChatGPTとCopilotにはとても助けられました**。

例えばデータ設計でChatGPTを使えば、テーブルに必要な要素を書いておいて「このテーブルになるようなmigrationファイルを提案してください」と言えば出力してくれます。これをmigrationファイルにコピペしてmigrateするだけでした。もしパッとみて馴染みがないデータ型であれば調べて、必要があれば修正しました。

コントローラの記述でも「SellerのServiceのidと一致するReview のデータを取り出したい」という旨の質問をしたときに、pluckやwhereInがでてきたので、**そこではじめて知ったメソッドもありました**。

### ５週目〜現在　プロダクトマネジメント

コピーしたものをもとに、今回の案件に必要な機能や絞り込んでいきました。

残り1ヶ月でリリース予定だったので、それまでの進行表をつくっていきました。また案件で中心となるシステムや、力を入れたい機能など選別していきました。

**プロダクトの方向性を考えてく過程ではコーディングとは違う頭の使い方をしました**。具体的にサービスを使う人がどんなことに注目するのか、どんなデータがあった方がいいのかなど、明確に答えが決まっていない分野だったので、難しいと感じることが多かったです。

## どう感じたか

エンジニア1ヶ月目**とても楽しく過ごしています**。理由は以下の通りです。

- 案件の開発をほぼ任されており、モチベーション高く仕事ができている
- 人間関係で一切悩まない。現状唯一の仕事仲間は社長だが非常に聡明でストレスがない
- 持病のナルコレプシー（どこでも寝る病気）があるが、それで人に迷惑をかけることがほとんどない
- おそらく適性があり、ずっと仕事してもそれほど苦ではない

あと自分が辛くなく続けられている理由として**ChatGPTとCopilotの存在は非常に大きい**と思います。

わからないことがぼんやりしててもChatGPTに質問を繰り返していると、自分がわかっていない知識の方向性がわかってきますし、内容を考えていること自体が問題の整理になってきます。

Copilotもモデルのリレーションなどはかなり正確にやってくれるので、データベースができた状態だとモデルにpublicと書くだけで予想してリレーションを書いてくれます。

逆に**ChatGPTとCopilotのない時代の新人エンジニアの方はとても大変だったんだろうな**と思います。

## 今後に向けて

- かなりの部分をChatGPTに聞いているので、技術力がついているのか不安に感じることが多いです。webサービスの主要な機能はきちんと理解できるようにしたいと思います。

- 社長としては今回自分を雇ってモデルケースにして、未経験の人でも雇えるような土壌を作りたいそうです。順調にこのまま進んであと３、4ヶ月たったら次は自分が教育係になって次の人を教えるかもしれないので、それまでに順当に技術力をつけていきたいです。

- スクールのメンターだったtom（[@tomoya_y0shida](https://qiita.com/tomoya_y0shida)）さんに「エンジニア１年目でやっておけばよかったと思うこと」を聞いたところ「英語でドキュメント読んでおけばよかった」とのことだったので、英語での読み書きに慣れるようにしていきます。

- 今後も研鑽を怠らず、しかし焦らずできることを増やしていきたいと思います。
mvn install が失敗する

備忘録  

# エラー  
```console
$ mvn clean package

[ERROR] Failed to execute goal on project ... 
from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [...] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException
```

# 原因
**maven-default-http-blocker** が肝。
maven3.8.1 よりpom.xml内のhttpでの指定がブロックされるようになった模様。  
[Maven3.8.1リリースノート](https://maven.apache.org/docs/3.8.1/release-notes.html#how-to-fix-when-i-get-a-http-repository-blocked)

# 対策
mvnの設定において、maven-default-http-blockerをスキップさせる。
※homebrewでmvnをインストールした環境
```shell
$ vi /opt/homebrew/Cellar/maven/3.9.2/libexec/conf/settings.xml
```

maven-default-http-blocker 箇所をコメントアウト。
```vim
<!--
<mirror>
	<id>maven-default-http-blocker</id>
	<mirrorOf>external:http:*</mirrorOf>
	<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
	<url>http://0.0.0.0/</url>
	<blocked>true</blocked>
</mirror>
-->

```
（エラーメモ）CloudRunにデプロイしたFastAPIでレスポンスクラスの宣言が異なるときの「Service Unavailable response」について

## 学び
それはそうだろという話ではあるけど、routeデコレーター引数のresponse_classをHTMLレスポンスで宣言しているにもかかわらず、実際はそのルータメソッドでFileResponseを返していると、HTTPレスポンスを上手くフォーマットできない場合がある。

今回Cloud Runでデプロイした場合は「Service Unavailable」が起きた。FastAPIもCloud RunもHTTP通信を明示的に扱うため、APIが宣言するものと実際に送信するものに不一致がある場合、予期せぬエラーにつながる。

## この実装をした背景
- FastAPIでフォーム送信すると、その内容に基づいて画像ファイルを生成して、ファイルの中身を画面で表示させるWebアプリを開発した（画面にはファイルの中身を表示させたいが、ファイルダウンロードさせたいわけではない。）
- ローカルでの動作確認やGAEやGCEでデプロイしたときは、response_classとレスポンスが異なっていても、Webアプリはいい感じにファイルの中身を表示してくれていたので、この雑なやりかたでも問題なかった。
- 例えばルータメソッドの中身はこんな感じ（エラーが起きる例）：
-

```Python
@router.post(
    f"***",
    response_class=HTMLResponse,
    response_description="***",
)
@handle_file_data_fetching_failed
async def show_***_file(
    document: DocumentSchema = Depends(DocumentSchemaAsForm),
    text: ***TextSchema = Depends(***TextSchemaAsForm),
    template_type: TemplateTypeSchema = Depends(TemplateTypeSchemaAsForm),
) -> FileResponse:
    return Single***Service(document, text, option, template_type).make()
```


## 改善後
ファイルの中身を読み込んでから、HTMLのコンテンツとして返す。

```Python
# application service
import base64

import aiofiles
from fastapi.responses import HTMLResponse


async def file_to_html_response(file_path: str) -> HTMLResponse:
    async with aiofiles.open(file_path, "r") as f:
        content = await f.read()
    encoded_content = base64.b64encode(content.encode()).decode()
    svg_data_url = f"data:image/svg+xml;base64,{encoded_content}"
    html_content = f"<img src='{svg_data_url}' alt='SVG Image' />"
    return HTMLResponse(content=html_content)
```
```Python
# routers
@router.post(
    f"***",
    response_class=HTMLResponse,
    response_description="***",
)
@handle_file_data_fetching_failed
async def show_***_file(
    document: DocumentSchema = Depends(DocumentSchemaAsForm),
    text: ***TextSchema = Depends(***TextSchemaAsForm),
    template_type: TemplateTypeSchema = Depends(TemplateTypeSchemaAsForm),
) -> HTMLResponse:
    file = return Single***Service(document, text, option, template_type).make()
    return await file_to_html_response(file.path)
```
いつも飲み過ぎちゃうから、飲みすぎないようにメールで注意してくれるようにしたお話。

この記事は[Qiita Engineer Festa 2023](https://qiita.com/official-campaigns/engineer-festa/2023) 参加記事です。
Qiita運営さんの[この記事誰得？ 私しか得しないニッチな技術で記事投稿！](https://qiita.com/official-events/5d4f04cf2ba0cdbc8821)企画への参加記事になります。
　
### この記事で書くこと

いつも飲み過ぎちゃう自分に対して、シラフの自分がしっかり注意してくれるようにしてみたので、そのお話です。
　
### この記事で書かないこと

注意された結果、どうなったのか？は書きません。
~~そんな注意、アルコールの前では無力です。~~


# いつも飲み過ぎちゃうから、飲みすぎないようにメールで注意してくれるようにしたお話。

## 準備

### 用意するもの
* Googleアカウント

### 使うもの
* Googleカレンダー
* Google Apps Script（通称GAS）

### 仕組み
毎朝、その日のカレンダーを取得して飲みの予定が入っていたら、飲み過ぎちゃダメよ！というメールを送る。

## 実際に作ってみる

### GASを書いてみよう。

[ここ](https://script.google.com/home)から作っていきます。
![GASはここから作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/f7d48854-612c-c50e-0d94-fe3aeab417cd.png)

実際のGASはこんな感じです。

```javascript
function sendAlertMail() {

  // 自分のカレンダー
  const myCalendar = 'jibun_no_mail@example.com';
  
  // カレンダーオブジェクト取得
  const calendarData = CalendarApp.getCalendarById(myCalendar);

  // 今日の日付を取得
  const today = new Date();

  // 今日の予定を配列で取得
  const schedules = calendarData.getEventsForDay(today);

  // メールを送信するかフラグ
  let sendMailFlg = false;

  // 予定がある場合のみ実行
  if (schedules.length != 0) {

    // 予定の件数だけ実行
    for (schedule in schedules) {

      // 予定の件名に飲み会っぽい文字がある時はメールを送る
      if(schedules[schedule].getTitle().includes('飲') || schedules[schedule].getTitle().includes('🍺') || schedules[schedule].getTitle().includes('🍻') || schedules[schedule].getTitle().includes('会食')){
        sendMailFlg = true;
      }

    }
  }

  // 飲み会がある時はメールを送信する。
  if(sendMailFlg){

    // メール件名
    const subject = '【要注意】本日は飲み会があります。';

    // 送付先メールアドレス
    const address = 'jibun_no_mail@example.com'

    // メール本文テンプレート
    const body = `本日は飲み会があります。

あなたは、過去何回も飲みすぎて失敗しています。

お酒は程々に`;

    // メール送信
    GmailApp.sendEmail(address, subject, body);

  }

}
```

### トリガーを設定してみよう

ここからトリガーの画面に行って
![トリガーはここから作る.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/1d3ca798-c2fd-0ae1-4602-1b89ff234e4e.png)

右下のボタンを押します。
![右下のボタンを押す.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/c4090516-3fea-0b7e-7f75-ce0be58bde20.png)

設定は、こんな感じ。
![トリガーはこんな設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/6f756721-d721-15b7-e30b-372f4c855963.png)
毎朝、AM9時から10時の間にメールを飛ぶように設定。

### とりあえず手動で動かしてみた

飲み会の予定がある日は・・・
![飲み会がある日は・・・.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/71645569-c2a1-0e00-5b69-bb22de19c871.png)

メールが飛んできた
![メールが飛んできた.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394384/5f9817ab-1957-1f78-dd9c-0c16d5182d71.png)

**できた**

# まとめ

勢いで書きました。
GASは、もっときれいに書けると思います。
というよりも、そもそもこんな通知が来なくても、お酒とは正しく付き合いましょう。
Steam deck（ChimeraOS）のMonster Hunter WorldでModを使用する

## 概要

Redditが閉鎖されるのが怖いので自分用にメモ

### 手順

Protonのバージョンを変更しないと手順がうまくいかなかったので最初に変更しています。

1. Monster Hunter Worldのプロパティから互換性を選択する
1. 特定のSteam Play互換ツールの使用を強制するにチェックを入れる
1. `Proton 6.3-8`を選択する
1. デスクトップモードに変更する
1. Discover appから`Protontricks`と`Flatseal`をインストールする
1. Flatsealを開く
1. 左メニューからProtontricksを選び、filesystemの項目までスクロールする
1. "all system files"を有効にする
1. Protontricksを開く
1. Monster Hunter Worldを選択し、"select the default wineprefix"を選ぶ
1. "Install a windows dll"を選択し、一覧からvcrun2019を選択して続行する
1. x86とx64用のインストーラが順番に起動するため、それぞれインストールする
    ここでエラーが出る場合、Protonのバージョンを変更して試してみてください
1. `Stracker's loader`と`performance booster`をダウンロードする
1. `Stracker's loader`と`performance booster`をMonster Hunter Worldのインストールフォルダに展開する
1. hid.dllからdtdata.dllへ名前を変更する
1. gaming modeに戻りMonster Hunter Worldを起動する

起動が確認出来たら好きなModを導入する
[Flutter] Flutter初心者の初心者のための記事

初めまして。
現在、Flutterを用いたAndroidアプリケーションを行なっている__Ten__と申します:relaxed:


Flutter環境構築をする上で詰まったところがありましたので、共有させて頂きます!



Flutter初心者なら誰しも一度は参考にするFlutter大学様の記事が元になっております。
URL:https://zenn.dev/kboy/books/ca6a9c93fd23f3/viewer/5232dc




さて、versionの変更による多少のレイアウト変更はありますが、Flutterのインストールはできるかと思います。

私が詰まったのは、「パスを通す」ところでした、、


参考記事のパスを通す④のところです。
記事では`:WQ`と入力しENTERで上書き保存できるとのことでしたが、以下のエラーが出てきてしまいました、、

E492: Not an editor command: WQ



`:WQ`ではなく小文字で`:wq`と入力することで無事に上書き保存できます！
初心者がAIチャレンジやってみた (1)：Autowareを動かしてみる

# 概要
Tier IVが開催しているAIチャレンジに挑戦してみた記録。
TLDR：パラメータ調整がなかなか難しい。

# セットアップ
セットアップは[公式ドキュメント](https://automotiveaichallenge.github.io/aichallenge2023-sim/setup/index.html)通りに進めて問題なくできた。

# パラメータ未調整（2023/6/19）
いざ、実行。１つ目のダンボールはちゃんと避けてくれた。
でもそのままコース外に飛び出すように走行していき停車…
![Screenshot from 2023-06-19 16-22-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3483805/c7d0101c-96b7-51ac-a0cd-a0d9a48e6067.png)

自ら障害物に当たりに行くスタイル。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3483805/269293e1-0c2d-7264-33bd-dd56b2506f25.png)

パラメータをいじらなくても実行するたびに結果が大きく変わる。

シミュレーションの様子は以下の動画を参照。最初の障害物はきれいに避けれくれるが、その次の障害物のことを考慮していないような経路になっている。
<iframe width="560" height="315" src="https://www.youtube.com/embed/46t8nnYzs10" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

感想
* どのパラメータをいじればいいのかが全くわからない。
* パラメータの数がめちゃくちゃ多く、ドキュメントにもあまり詳細が書いていない。
* 実行するたびに結果が変わるのでパラメータを調整した結果なのか、たまたまそうなっただけなのかが分からない。
[PostgreSQL] 異なるversionのpsql, pg_dump コマンドを使用する

Ubuntuで確認済み。元々ver11.xを使っていて、ver12.xを使用したいとする。
もっといい方法があるかも。

## 準備する

```sh
$ sudo apt update
$ sudo apt install postgresql-client-12

# Confirm that version of the commands are 12, not 11.
$ /usr/lib/postgresql/12/bin/pg_dump --version
pg_dump (PostgreSQL) 12.15 (Ubuntu 12.15-1.pgdg22.04+1)
$ /usr/lib/postgresql/12/bin/psql --version
psql (PostgreSQL) 12.15 (Ubuntu 12.15-1.pgdg22.04+1)
```

## 使用する

絶対パス指定で `/usr/lib/postgresql/12/bin/psql` コマンドを使い、やりたいことをする。


## 後片付けする

```sh
$ sudo apt remove postgresql-client-12

# Confirm that commands found in your command paths is version 11.
$ psql --version
psql (PostgreSQL) 11.19 (Ubuntu 11.19-1.pgdg22.04+1)
$ pg_dump --version
pg_dump (PostgreSQL) 11.19 (Ubuntu 11.19-1.pgdg22.04+1)
```
AzureのPaaSを用いたWeb開発で起きたタイムゾーン問題

## 環境
### Azureリソース
Web AppsとPaaSのAzure SQL Serverを使用
### フレームワーク
フロントエンドをVue.js、バックエンドをNode.jsのExpressで作成

## 何が起きたか
「登録日時」などのDATETIME型の列に登録した日付の値が、登録したかった値の9時間前になっていました。

## 注意点その１．DB側のタイムゾーン
`GETDATE()`などを用いる際はDBサーバのタイムゾーンを基に時間を取得します。
Azure SQL Serverは***タイムゾーンが指定できない***かつ***デフォルトがUTC***です。

対応策としては`AT TIMEZONE`を日付型の列やGETDATE()の後ろに付けてあげると、タイムゾーンがTokyoとして扱われます。
画像はUTCのタイムゾーンをTokyoに変更するクエリです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781188/b9065576-4998-9156-7fb2-89317622832f.png)

※付けた場合と付けなかった場合の比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781188/73e989b4-414c-b26d-5a43-a0e3de1d4a77.png)

比較についてはタイムゾーンを考慮しているようです。例えば
`2023-06-19 12:00:00`＜`2023-06-19 16:00:00 +09:00`がFalseになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781188/370d1656-1346-fe57-372d-528c0ee8bfbf.png)

また、システムから登録日時などをINSERTする場合にUTCから＋9時間した時刻をパラメータの値にしたうえで、更に`AT TIME ZONE`でTOKYOの時刻に変換すると本来の時刻より+9時間された値となってしまいます。

「東京基準の2023年6月19日午前10時」の想定で`2023/6/19 10:00:00`を値として登録
↓
`AT TIME ZONE`を付与してクエリ実行
↓
`2023/6/19 19:00:00 +09:00`と表示され、「東京基準の2023年6月19日午後7時」になってしまう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781188/4ff60b35-e1e8-af8f-fcd0-97043acb72a4.png)

### クエリで`AT TIME ZONE`を付与する場合のプラクティス
- `GETDATE()`や日付型の列などに対して漏れなく`AT TIME ZONE`を付与する
- タイムゾーンの指定なしで日付を列に登録する場合は、UTCの時刻を登録する
- `AT TIME ZONE`を指定してない時の時間がUTC時刻になるように統一する

## 注意点その２．WEBサーバ（WebApps)側のタイムゾーン
また、WebAppsを用いて稼働させているコンテナのタイムゾーンも同様にUTCであるため、サーバサイド側の処理でシステム時刻を用いるとUTCの時刻を用いて処理を行う事になってしまいます。
dayjsの場合はプラグインをimportすることでタイムゾーンを指定して時刻を扱うことが可能になります。

https://zenn.dev/taigakiyokawa/articles/20221122-dayjs-timezone

### クエリで`AT TIME ZONE`を付与しない場合のプラクティス
- `GETDATE()`などのDBサーバのタイムゾーンに依存する物を使用しない（あるいは時間を+9して使う）
- `AT TIME ZONE`を指定してない時の時間がTokyo時刻になるように統一する
SDカードで「フォーマットする必要があります」と出てしまう場合に試してほしい対応（Windows）



最近ミラーレス一眼カメラを購入し、外に出かけて写真を撮ることが増えてきました。

出先でSDカードリーダーをスマホに差し、撮った写真を確認しようとすると、「フォーマットする必要があります。フォーマットしますか？」というメッセージが表示されました。

今回、ネットで調べて自己解決に至ったので、覚書していきます。

## 対処方法


この時、焦ってSDカードをフォーマットしてしまわないようにしましょう。

※フォーマットを選択すると、データがすべて削除されてしまいます。

「フォーマットする必要があります。フォーマットしますか？」というメッセージが表示されている場合、

**1.**　スマホやパソコンの不具合
**2.**　SDカードの容量不足　
**3.**　正しい手順でSDカードを取り外さなかった

等が考えられます。

1の場合、スマホやパソコンを再起動することで解消する場合があります。2の場合、ぎりぎりまで容量を使わない他思いつかないです…。

さて、本題ですが、経験上1や2よりも、圧倒的に3が原因なことが多いです。<font color="red">**(SDカードの取り外しは「ハードウェアの安全な取り外し」から行いましょう！)**</font>

その場合、SDカードが論理故障してしまっている状態なので、スマホやパソコンをいくら再起動しても直りません。

そこで試していただきたい方法が、WindowsPCに搭載されているツール「CHKDSK（チェックディスク、check diskの略語）」を使う方法です。

ハードディスクにエラーがないかチェックしたり、エラーを修復したりしてくれるツールです。



## 手順

#### 1. 修復したいSDカードをパソコンに接続する
#### 2. 画面左下の検索バーに、「コマンド プロンプト(もしくはcmd)」と入力する
#### 3. コマンド プロンプトを右クリックして、管理者として実行を選択する
#### 4. ウィンドウで「chkdsk <font color="red">D</font>: /f」を入力してEnterキーを押す
<br>

※**chkdsk**のあとの<font color="red">**D**</font>はSDカードのドライブ名です。必要に応じて変更してください。
　対象ドライブ名はエクスプローラーの「デバイスとドライブ」から確認するか、設定から確認できます。
※実行は自己責任でお願いします。

## おわりに

「フォーマットする必要があります。フォーマットしますか？」というメッセージが表示されてしまってデータが見られなくなってしまった場合の対応を紹介しました。

案外この問題で悲しんでいる方がいるのではないかと思い、記事としてまとめてみました。

手順としてはとても簡単な操作ですので、よろしければお試しください。


<br>


***

#### 参考にした記事：
* https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/chkdsk?tabs=event-viewer
* https://cybersecurity-jp.com/column/57305
* https://www.reneelab.jp/repair-damaged-sd-card.html#c
* https://www.diskpart.com/jp/articles/command-prompt-check-disk.html
* https://pctrouble.net/running/chkdsk.html
TypeScriptとは？どのような言語で何ができるのかをわかりやすく解説

　TypeScriptは、Microsoftによって開発された静的型付け言語であり、JavaScriptの拡張として広く利用されています。本記事では、TypeScriptの基本的な特徴や利点、使い方について説明します。初めて見る人でもわかりやすく、TypeScriptについて理解を深めることができるでしょう。

1. TypeScriptとは何か
　TypeScriptは、JavaScriptに静的型付けとクラスベースのオブジェクト指向を加えたプログラミング言語です。静的型付けにより、変数や関数の型エラーを事前に検出できるため、開発の効率性や信頼性が向上します。また、JavaScriptとの互換性があり、既存のJavaScriptコードをそのまま利用しながら徐々にTypeScriptに移行することも可能です。

2. TypeScriptの特徴と利点
① 静的型付け
TypeScriptでは、変数や関数に型アノテーションを付けることができます。これにより、開発者はコードを書く段階で型エラーを検知することができ、実行時のバグを減らすことができます。
② オブジェクト指向
TypeScriptはクラスベースのオブジェクト指向言語であり、クラスやインターフェースをサポートしています。これにより、コードの再利用性や保守性が向上し、大規模なプロジェクトでも効果的に開発することができます。
③ツールのサポート
TypeScriptは、豊富な開発ツールとの統合が可能です。IDE（統合開発環境）やエディタは、静的型付けに基づく補完やエラーチェック、リファクタリングなどの機能を提供します。さらに、コードの品質管理やドキュメンテーション生成にも役立つツールが提供されています。
④ECMAScriptへのコンパイル
TypeScriptは、ECMAScript（JavaScriptの標準化バージョン）へのコンパイルが可能です。開発者はTypeScriptでコードを記述し、コンパイラによってECMAScriptに変換されたコードを実行することができます。これにより、ブラウザやNode.jsなど、さまざまな環境で動作する高性能なJavaScriptコードを生成することができます。

3. TypeScriptの使い方
①インストールとセットアップ
TypeScriptを利用するには、まずTypeScriptのコンパイラをインストールする必要があります。npm（Node.jsのパッケージ管理ツール）を使ってインストールすることができます。

```
npm install -g typescript
```

　インストール後、TypeScriptファイル（拡張子.ts）を作成し、コンパイルすることでJavaScriptファイル（拡張子.js）が生成されます。

②型アノテーションの使用
　TypeScriptでは、変数や関数の型を明示的に指定することができます。例えば、数値型の変数を宣言する場合は次のように記述します。

```
let count: number = 5;
```

　また、関数の引数や戻り値にも型アノテーションを付けることができます。

```
function add(a: number, b: number): number {
  return a + b;
}
```

③クラスとインターフェースの使用
　TypeScriptでは、クラスとインターフェースを使用してオブジェクト指向プログラミングを行うことができます。クラスはオブジェクトの設計図として機能し、インターフェースはクラスやオブジェクトの型を定義します。

```
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

interface Shape {
  area(): number;
}
```

④コンパイルと実行
　TypeScriptファイルをコンパイルするには、コマンドラインで次のコマンドを実行します。

```
tsc filename.ts
```

　これにより、ECMAScriptに変換されたJavaScriptファイルが生成されます。生成されたJavaScriptファイルを実行することで、TypeScriptコードが実行されます。

＜まとめ＞
　TypeScriptは、JavaScriptの拡張言語として開発された静的型付け言語です。静的型付けやクラスベースのオブジェクト指向などの特徴により、コードの品質や開発効率を向上させることができます。また、JavaScriptとの互換性があり、既存のコードを容易に移行することができます。是非、TypeScriptを学んで、より堅牢なJavaScriptアプリケーションの開発に役立ててください。
プライベートサブネットのRDSへの接続方法

# はじめに
今回は、プライベートサブネットのRDSの接続方法についてまとめてみたので共有します。
新サービス「EC2 Instance Connect」 を利用した接続方法についても調査したので必見です。

# RDSの接続方法
以下に、4つの接続方法を紹介します。
それぞれメリット・デメリットがあるため、システムに合った方法を選択することをおすすめします。

## ① EC2を踏み台サーバとして利用する
1つ目は、EC2を踏み台サーバとして利用する方法です。
接続方法は、このようになります。
* プライベートサブネットに踏み台サーバとしてEC2インスタンスを作成し、SSM Agentをインストール
* 踏み台サーバには、Session ManagerまたはFleet Managerでアクセス
* EC2にDBクライアントをインストール
* 踏み台サーバのDBクライアントツールからRDSに接続


※クライアントツールにて、
* GUIを使いたい場合は、Windows + Fleet Manager
* CUIを使いたい場合は、Linux + Session Manager

で踏み台サーバを作成ください。

### メリット
* EC2は利用しないときに停止しておくことで、料金を抑えられる
* 操作ログを管理できる

### デメリット
* EC2の管理が必要になる

## ② EC2をポートフォワーディングに利用する
2つ目は、EC2をポートフォワーディングに利用する方法です。
接続方法はこのようになります。
* プライベートサブネットに配置したEC2にSSM Agentをインストール
* 手元のクライアント端末（EC2でない）のDBクライアントから、EC2を経由してRDSに接続
※EC2が、SSH(22)で届いたポートをRDSのポート(3306や5432）に転送

### メリット
* EC2は利用しないときに停止しておくことで、料金を抑えられる
* 操作ログを管理できる

### デメリット
* EC2の管理が必要になる
* クライアント端末からデータを操作できるため、機密性の高いデータを扱う際には注意が必要になる


## ③ Client VPNを利用したVPN接続
3つ目は、Client VPNを利用したVPN接続です。
接続方法は、このようになります。
* Client VPNを利用し、RDSへクライアント端末から接続する。

### メリット
* 通信はすべて暗号化されるため、セキュア
* マネージドサービスのため、管理の手間が省ける


### デメリット
* ①、②と比べるとコストが高い
* Client VPNのランニングコストは2つに大別されます。
    * エンドポイントアソシエーション：0.15USD/時間
    ※通常は、24/365稼働となるため、関連付けるサブネットごとに、約15,000円/月となります。
    * 接続時間：0.05USD/時間

 * 証明書管理によって、運用コストが変わる

## ④ EC2 Instance Connect Endpointを利用する
4つ目は、EC2 Instance Connect Endpointを利用する方法です。
→こちらは2023年6月より利用できなくなりました。

* EC2 Instance Connect Endpointを作成
* EC2 Instance Connect Endpointに紐づけるセキュリティグループ設定で、RDSのポート(3306や5432）のアウトバウンド通信を許可
*  RDSインスタンス側のセキュリティグループでVPC Endpointからのインバウンド通信を許可
*  クライアント環境でAWS CLIを利用できるよう初期設定
*  EC2 Instance Connect Endpoint経由でRDSへ接続

### メリット
* EC2の管理が不要で、手間が省ける
* EC2 Instance Connect Endpointは利用料金が無料のため、①、②と比べコストが安価に
    ※データ転送料は課金される


### デメリット
* 操作ログを収集する機能が提供されていない（ログはAWS CloudTrailのみに出力される）
* 1つのVPCに１つのEC2 Instance Connect Endpointしか作成できないため、EC2 Instance Connect EndpointのエンドポイントがあるサブネットにAZ障害が起きると使用できない




# おわりに
こちらの記事を参考にまとめさせていただきました！

https://dev.classmethod.jp/articles/pc-to-rds/

https://dev.classmethod.jp/articles/how-to-connect-rds-instance-via-eic-endpoint/
Apacheからnginxに移行時、表示がApacheのままの時の修正方法

# 自分なりのやり方
Apacheをアンインストールしてnginxに移行したつもりだったんですが、まだApacheのファイルが残っていたらしく、

```bash
sudo apt-get remove apache2

sudo apt-get purge apache2
```
で設定ファイルごと消したらいけました。

大変。。。

# 参考

https://arkgame.com/2022/05/12/post-307955/
Filemakerで作ったzoom予約機能の認証方式を変更したお話

## はじめに
以前、「[Filemakerでzoom予約機能を作ったお話](https://qiita.com/mihoT/items/242ac68bb1623766ffcc)」という記事を書き、これまで順調に・快適にFilemakerでzoom予約機能を使っていたのですが、さぁ今年もzoomのAPI認証に必要なJWTトークンの更新をしようかな～と作業しようと思ったらなんと・・・！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/9680c887-fb93-e319-ca70-b95de72655e5.png)
zoomのJWTトークン認証方式は、2023年9月1日に廃止されるんですって!?（2023年6月1日に新規作成を停止）知らなかったーーー！

といっても、１年も前に告知されていたことを今更知り、なんなら１年前、当時のCTOからお知らせまでもらっていたのにそれにも気が付かなかったという、完全に私の落ち度しかない話です・・・すみません。

というわけで、急ぎ対応しましたので、記録します。

## 認証方式の変更
JWTトークンは廃止になるので、Server-to-Server OAuth方式か、OAuth方式に変更してね、ということなので、とりあえず、急いでいるのでServer-to-Serverで試します。

変更前、変更後のイメージ図は、こんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/a8eb1490-e3b6-37f2-af36-1c097b1b14da.png)

### Marketplaceでのアプリ作成
「[はじめての Zoom API - Server-to-server OAuth編](https://qiita.com/yosuke-sawamura/items/fa036273a161b190478b#-marketplace%E3%81%B8%E3%81%AE%E7%99%BB%E9%8C%B2%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) のQiita記事を参考に進めました。ので、説明は割愛します。

１点だけ、Scopesを選択する画面で、私はzoomのMeetingを作る作業がしたいので、```meeting:write:admin```の権限がもらえそうな、「View and manage all user meetings」にチェックを入れました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/0fa7815e-d8c9-9818-56ef-8545a556e634.png)

### Client ID と Client Secret をbase64エンコードする
Marketplaceで取得したClient ID と Client Secretを「:」でつなぎ、base64エンコードしたキーを大事に持っておきます。
```
echo -n clientID:ClientSecret | base64
```

### Filemakerのzoomミーティング作成APIにひと手間加える
#### 必要なフィールドを３つ追加
- account_id → Marketplaceで発行されたAccount idをセット
- base64key → 上のclientID:ClientSecretをbase64エンコードしたキーをセット
- zoom_prepare_json → zoomに問い合わせをして返ってきたjsonを格納するためのフィールド

#### 必要なスクリプトステップ
URLから挿入
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/954a7f7a-a341-b9e4-12b9-276a93daa6a0.png)
- ターゲット：zoom_prepare_jsonフィールド
- URLを指定：```"https://zoom.us/oauth/token?grant_type=account_credentials&account_id=" & [account_id]```
- cURLのオプション：```"--request POST --header \"Authorization: Basic " & [base64key] & "\""```

このステップを実行すると、zoom_prepare_jsonに、ミーティング作成に必要なaccess_tokenが含まれたjsonが返ってくるので、JSONGetElement関数を使って、access_tokenの値を取り出して、変数にセットします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/df07b04e-d79e-b69b-b018-8a420311212a.png)

この変数をミーティング作成スクリプトのbearer_tokenにセットしてあげれば、これまで通り、ミーティング作成できるようになります。以前私の書いた記事でいうところの[STEP.3](https://qiita.com/mihoT/items/242ac68bb1623766ffcc#step3-filemaker%E3%81%A7%E3%83%9D%E3%83%81%E3%81%A3%E3%81%A8%E3%81%99%E3%82%8B%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E4%BD%9C%E3%82%8A%E3%81%BE%E3%81%97%E3%82%87%E3%81%86)で、cURLのオプションを設定する箇所を、JWTトークンでなく、今回作ったaccess_tokenの値にしてあげればOKです！

### スクリプトの全体像
こんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603563/0d944bd4-5770-2964-d8a6-b5e49b7488d4.png)


## おわりに
急いで認証方式を更新したので、いろいろ説明が雑でしたが、最後まで読んでいただきありがとうございました。zoomのJWTトークン認証廃止を目前に、大きな修正を入れることもなく改修ができてよかったです。Filemakerの「URLから挿入」とJSON成形系の計算式、改めてありがたや！！そして、便利で簡単に使えるzoomの存在もありがたや！！と思いました。


## 参考にさせていただいた記事
https://qiita.com/yosuke-sawamura/items/fa036273a161b190478b
canary状態のPodを識別したい - Ephemeral Metadata（エフェメラルメタデータ）

# 背景
ArgoRolloutsを利用し、Canary Releaseの動作検証をおこなっていました。
その中で、PrometheusのQueryを利用し、canary状態のPod（コンテナ）のデータ（例：CPU使用量）を取得するシナリオを考えていました。
取得したデータの値によって、成功 or 失敗を判断し、canaryからstableに昇格もしくは前バージョンにrollbackさせることが目的です。

# 悩んだこと
ここで悩んだのが、canary状態のPodを認識する方法です。

Pod名やコンテナ名でクエリーすると、canaryとstableの両方のデータが取れてしまいます。canaryのPodだけを識別するにはどうすれば良いのだろう？と悩んでしました。

# 解決方法
そんな中、 **Ephemeral Metadata（エフェメラルメタデータ）** という機能を見つけました。
Rolloutリソースのマニフェストに、`stableMetadata`と`canaryMetadata`を指定でき、ラベルやアノテーションを設定できます。

`canaryMetadata`に指定したメタデータは、canary状態の間だけ適用され、stable状態になったら自動で削除されます。
そして、`stableMetadata`に指定したメタデータが設定されます。

これを使うことで、事前に定義したラベルを使って、canary状態のPodのデータのみを取得することが可能になりました。

# 参考
Ephemeral Metadata
https://argo-rollouts.readthedocs.io/en/release-1.5/features/ephemeral-metadata/
めちゃくちゃ安価なPLC　Arduino OPTAを使ってみた！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/fcff74f7-f0ca-ebba-b9ff-7d6f2218e429.png)

今回お客様より「安価なPLCを使ってプログラムを組めないか？」との要望を受け、
初めてArduino OPTAを使用することになった！

## Arduino OPTAとは
Arduino社が産業用に開発したPLC製品です。
言語で開発できるので、今までラダープログラムが苦手だった複雑な演算処理も
簡単に組むことが出来ます！
**しかも値段が3万もしません！！**

安すぎる・・・
これが出回りだすと大手のPLC販売メーカーは困るでしょうね。
https://akizukidenshi.com/catalog/g/gM-17627/
↑購入先

## 環境構築
OPTAの+、-配線に12V-24Vの電源を供給する必要があります。
今回は安定化電源を使用して12Vを供給しました。
起動時の電流は45mA程度でした。
（ちなみに電源を接続しなくてもUSB接続をすればUSBから電源が供給されるようです。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/fa19ab75-e288-e73b-02be-9a0b3a6294d4.png)

## 開発環境
開発環境は、ArduinoIDE 2.1.0を使用しました。
まずツール＞ボード＞ボードマネージャーを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/8e76ebe2-e253-b64c-df7d-70da244e13e6.png)

検索のところで「Opta」を入力。
「Arduino Mbed OS Opta Boards」をインストールする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/62dd94e7-8a85-88e2-dce9-2f321e806a07.png)

インストールが完了すればツール＞ボード＞Arduino Mbed OS Opta Boards＞Opta
を選択すれば開発環境はOKです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/6c99fddc-cc0a-a5a2-76f5-00f67d5cfe1b.png)

## Lチカしてみる
それでは手始めに入門で定番のLチカをしてみましょう。
書き込み設定は、以下の画像を参考にして下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/57a9f146-07f5-810d-6b34-1859d5e32507.png)


OPTAは始めからSTATUS LEDがあるので今回はそれを使用します！
```c
void setup() {
  // put your setup code here, to run once:
  pinMode(LED_D0, OUTPUT);
  pinMode(LED_D1, OUTPUT);
  pinMode(LED_D2, OUTPUT);
  pinMode(LED_D3, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(LED_D0, HIGH);
  digitalWrite(LED_D1, HIGH);
  digitalWrite(LED_D2, HIGH);
  digitalWrite(LED_D3, HIGH);
  delay(1000);
  digitalWrite(LED_D0, LOW);
  digitalWrite(LED_D1, LOW);
  digitalWrite(LED_D2, LOW);
  digitalWrite(LED_D3, LOW);
  delay(1000);
}

```
なんのひねりもない1秒毎にLEDが点滅を繰り返すプログラムです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/9719c6d3-0cd0-1acc-d6fa-3a68cada3ca3.png)



## アナログ入力してみる
以下のﾌﾟﾛｸﾞﾗﾑを書き込み
I1と-の端子に10Vを入力してみます
```c
void setup() {
  // ｼﾘｱﾙ通信を設定
  Serial.begin(9600);

  // ｱﾅﾛｸﾞ入力の分解能を16bitに設定
  analogReadResolution(16);
}

void loop() {
  // 1chの電圧取得
  int ValueA0 = analogRead(A0);
  float volA0 = ValueA0 * (3.0 / 65535.0)/ 0.3;
  Serial.print("I1 value: ");
  Serial.println(ValueA0);
  Serial.print("I1 Vol: ");
  Serial.print(volA0, 5); 
  Serial.println("V");

  delay(1000);
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/f068bf20-7dbd-cf25-5052-de5cc9b0c8b7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/e6c0f375-6016-998d-78e2-9af968d838e0.png)
10V入力したのですが値は、9.12～9.13Vをﾊﾟﾗﾊﾟﾗしています。
端子間にｺﾝﾃﾞﾝｻ　or ﾌﾟﾛｸﾞﾗﾑで平均処理が必要みたいです。
あと係数の調整も必要ですね。


## 感想
通常PLCは、ﾗﾀﾞｰﾌﾟﾛｸﾞﾗﾑで作成しますが言語で作成できるのは、色々と使える幅が広がりそうです。
また他社のPLCと比べると非常に安価です。
まだどのくらいの耐久性があるのか分かりませんが、ちょっとしたところに使用するのは
非常に良い製品かもしれません。
Arduino社自体は、世界的に有名な企業なので信頼性は高いと思います。

安価でちょっとしたところに使用できる製品！
開発依頼をしたい方はお気軽にご相談下さい！
SonarQubeで特定の解析ルールを特定のディレクトリに対して無効化する方法はあるのか調査

調べてみました！

# 結論1 ドキュメントを読め
書いてあったわ
https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/

# 結論2 sonar-project.properties に書く
> You can set specific files and rules under sonar-project.properties with following contents:
```
# Ignore Issues
sonar.issue.ignore.multicriteria=e1,e2
# Skip Bold Check
sonar.issue.ignore.multicriteria.e1.ruleKey=Web:BoldCheck
sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.html
# Skip Tag Check
sonar.issue.ignore.multicriteria.e2.ruleKey=Web:S1234
sonar.issue.ignore.multicriteria.e2.resourceKey=**/*.html
```


# 結論にたどり着く経緯
- 日本語で調べても有益な情報が出てこない印象。
- 「sonarqube rule exclusion」で検索
- https://stackoverflow.com/questions/21103175/how-to-disable-sonar-rules-for-specific-files が出た
- https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/ も出た

以上
Go言語とTypeScriptを使ったJSONの送受信：Goのmap[string]interface{}からTypeScriptのfetchまで

## はじめに

Go言語の学習をしているものです。Go言語を使ったサーバーサイドのコードからTypeScriptを使ったクライアントサイドのコードにレスポンスとしてJSONデータを送りたかったのですがその際に`map[string]interface{}`型のJSONデータを送信する場合があることについて学んだのでそのことを備忘録がてらに投稿します。

また、この記事で紹介するコードは`openweathermap api`を用いたデータのやり取りを想定しています。

## GoでJSONデータを送信

Go言語は、JSONエンコーディングとデコーディングのための強力な機能を提供します。GoでJSONを生成する基本的な方法は、json.NewEncoder関数を使用することです。以下に例を示します。

```diff_go
json.NewEncoder(w).Encode(map[string]interface{}{
	"temperature": celsius,
	"condition":   weatherResponse.Weather[0].Description,
})
```

ここで、`map[string]interface{}`型を使用しています。この型はGoにおける汎用的なデータ構造で、JavaScriptにおけるオブジェクトと同様にキーと値のペアを保存します。キーは文字列で、値は任意の型のデータを保存できます。

なお、`interface{}`はGoの"空のインターフェース"で、任意の型の値を受け入れることができます。これにより、`map[string]interface{}`型は、キーが文字列で値が任意の型のデータを保存できる汎用的なデータ構造となります。この特性により、特定のデータ構造を持たない汎用的なJSONデータを生成してクライアントに返すことが可能となります。

具体的には、Go言語ではインターフェースはメソッドの集合を定義しますが、`interface{}`はメソッドを一つも持たないインターフェースです。このため、任意の型の値はすべてこのインターフェースを"実装"します。つまり、`interface{}`は任意の型の値を受け入れることができる一種の"万能型"と言えます。

## TypeScriptでJSONデータを受信

クライアントサイドでは、Fetch APIを使ってサーバーからデータを受け取ります。Fetch APIはPromiseベースのAPIで、レスポンスボディを各種フォーマットで読み取るためのメソッドを提供します。JSONデータを受け取る場合は、`response.json()`メソッドを使います。以下に例を示します。

```diff_go
import React, { useState } from "react";

type WeatherItem = {
  temperature: number;
  condition: string;
};

export default function Home() {
  const [city, setCity] = useState<string>("");
  const [weather, setWeather] = useState<WeatherItem | null>(null);
  const [error, setError] = useState<string | null>(null);  

  const getWeatherData = () => {
    setWeather(null);
    setForecast(null);
    setError(null);

    fetch(`http://localhost:8080/current-weather?city=${city}`)
      .then(response => {
        if (!response.ok) {
          if (response.status === 404) {
            throw Error("そのような都市名は存在しません");
          } else {
            throw Error(response.statusText);
          }
        }
        return response.json();
      })
      .then(data => setWeather(data))
      .catch(error => setError(error.message));
  };

  const handleCityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setCity(event.target.value);
  };

  return (
    <div>
      <h1>Weather App</h1>
      <input type="text" value={city} onChange={handleCityChange} />
      <button onClick={getWeatherData}>Get Weather</button>
      {error && <div>Error: {error}</div>}
      {weather && (
        <div>
          <h2>Current Weather</h2>
          <p>Temperature: {weather.temperature}</p>
          <p>Condition: {weather.condition}</p>
        </div>
      )}
    </div>
  );
}
```

`response.json()`メソッドは、レスポンスボディの内容をJSONとして解析し、その結果をPromiseとして返します。この解析結果（JavaScriptのオブジェクト）は、次の`.then`ブロックに渡されます。上記の例では、解析したJSONデータを`setWeather`関数に渡しています。

このように、Fetch APIとPromiseを組み合わせることで、非同期的にサーバーからデータを取得し、その結果を効率的に処理することができます。

## まとめ

この記事では、Go言語とTypeScriptを用いて、サーバーとクライアント間でJSONデータを送受信する方法について解説しました。Go言語の`map[string]interface{}`という柔軟なデータ構造を利用することで、任意のJSONデータを生成し、これをサーバーからクライアントへ送信することができます。そして、クライアントサイドではFetch APIを用いて、このデータを非同期に受け取り、適切に処理することができます。
WindowsでScalaの開発環境を構築する

### Javaインストール
JDKインストール～パスを通すまで
https://web-camp.io/magazine/archives/56775

### sbtインストール

https://scalapedia.com/articles/14/Windows%E3%81%ABsbt%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95

PathはJavaと同様に設定

### IntelliJ IDEAインストール

こちらからCommunity版のIDEAをダウンロード

インストール手順は下記参考
https://sukkiri.jp/technologies/ides/intellij-idea/intellij-idea-win.html

プラグインでScalaを設定

### コマンドプロンプトでScalaを使いたい
https://www.casleydi.com/blog/engineer/101/

Failed to initialize compiler: object java.lang.Object in compiler mirror not found.

JDKとScalaのバージョンが合っていなかった
https://docs.scala-lang.org/overviews/jdk-compatibility/overview.html

Scalaのバージョンを上げて再度実行
Spring Batch 複数job作成時の実行メモ

### Spling Batch で複数Job定義したときの実行job指定方法

ソースは公式のサンプルを使用

https://spring.io/guides/gs/batch-processing/

## Job定義
```java:BatchConfiguration.java
	@Bean
	Job firstJob(JobRepository jobRepository,
			JobCompletionNotificationListener listener, Step step1) {
		return new JobBuilder("firstJob", jobRepository)
				.incrementer(new RunIdIncrementer())
				.listener(listener)
				.flow(step1)
				.end()
				.build();
	}

	@Bean
	Job secondJob(JobRepository jobRepository,
			JobCompletionNotificationListener listener, Step step1) {
		return new JobBuilder("secondJob", jobRepository)
				.incrementer(new RunIdIncrementer())
				.listener(listener)
				.flow(step1)
				.end()
				.build();
	}
```

このまま実行するとエラーになる
```
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner': Invocation of init method failed
・・・
Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs
```

## Job指定
下記の"18.12.2. Running Spring Batch Jobs on Startup"に記載のとおり、実行するJobは、"spring.batch.job.name"に指定する。

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.batch.running-jobs-on-startup


```properties:application.properties
spring.batch.job.name=firstJob
```
※application.propertiesはcomplete/src/main/resourcesに配置

## 引数にJob指定
起動時の引数にも設定可

```
--spring.batch.job.name="secondJob"
```

## ~~複数Job実行指定~~
今は１回の実行でひとつのJobになってるみたい...

https://github.com/spring-cloud/spring-cloud-task/issues/894

```properties:application.properties
spring.batch.job.name=firstJob, secondJob
```
~~※実行時の順番はプロパティファイルに記載した通りになるわけではないらしいので注意、Spring Batch では制御できないらしい~~
Pythonを酒井さんのUdemyの動画で学ぶ際の注意点

今回は私の経験から、Udemyで酒井さんの講座を学び、躓いている人に向け、改善策と注意点を書かせていただきました。注意点は2点あると思います。

## 1.注意点その1

1点目は、酒井さんの講座では、メソッドを学ぶ前から、様々なメソッドを紹介しているという点です。メソッドという言葉を聞いたことがない、聞いたことはあるがよくわからない人は講座を最初のほうから順に受講していくとあれれなんか難しいな。ってなると思います。（自分はなんかしらんの出てきた。しかもめっちゃむずい。プログラミングむずかしい。となりました）

これの改善策として、単純ですが、Pythonの基礎文法である「関数」と「クラス」を勉強してから、酒井さんの講座を受ける。のがいいと思います。具体的に書いていきます。

酒井さんの講座では、基本的な文字列やリストなどについて学習する際、同時に文字列のメソッドや、リストのメソッドまで紹介してくれています。
なのでPythonを0から学び始めた人が最初から講座を受けていると、「文字列やクラスは今学んだからわかるけど、メソッドってなに？」となって、躓いちゃうんじゃないかなと思います。

（メソッドの一例は下のコードのようなもの）
```Python 
n.append(2)
```


自分は[Paizaの教材](https://paiza.jp/works/python3/primer/beginner-python8 "paizaでpythonの基礎文法を学べるページ")でPythonのクラスについて勉強してから、酒井さんの講座を受けなおしたら、酒井さんが言っていることがスムーズに理解できるようになりました。





## 2. 注意点その2

2点目は、ボリュームが多いということ。これは通常良いことなのですが、全くの初心者にとっては、一つの項目で、これもこれもこれもこれもと紹介されると、覚えきれないよとなるかもしれません。







## 3.注意点を解消するために提案

注意点1では関数やクラスやメソッドを学んでから、酒井さんの講座を受けるとよいと書きましたが、関数やクラスはpythonの基礎文法の中でも最後に学ぶ内容で、それ以前の内容がわかっていないと、それらを理解することも難しいと思います。
そして注意点2では知識ゼロの人が学ぶには、細かいところまで紹介されていて、覚えること多いなってなってしまう恐れがあると書きました。
なのでこれらを解決するために、一度Pythonの基礎文法をほかの軽めの教材で学び、それから酒井さんのUdemyのコースを学ぶというのがおすすめです。自分はそうしてようやく酒井さんの講座で学習ができるようになりました。もし全くの初心者で私と同じように躓いている方がいたら参考にしてみてください。
vpn via ssh（ssh接続さえできればvpn接続が簡単にできる）【備忘録】

# 学校wifiでよくわからないけど、npx create-next-appに数十秒かかってnpmがブロックされていると勘違いして頑張ったので、備忘録です。
vpn張っている間は一瞬で通るので利点はあります。

# 方法
```bash:debian(wslのubuntu)
sudo apt-get install sshuttle
sudo sshuttle -r docker_user@***.***.***.***:443 0.0.0.0/0 --dns
```

0.0.0.0/0で全てのwifiにしています。
docker_userとなっている部分は自分のsshのuserにしてください。
***.***.***.***は自分のipアドレスにしてください。
443となっている部分はポート番号にしてください。
というかsshコマンドのときと同じです。
後は、sshのパスワードを打ち込めば終わりです。
別のターミナルを開いてコマンドを打つと、vpnがつながっていることがわかります。

# テストコマンド
```bash:wsl
curl https://geo.risk3sixty.com/me
```
このコマンドでipアドレスから自分の場所とかをJsonで出してくれます。
vpn越しの場合は`"city":"Tokyo"`になっていたので、私はナウいヤングです。

# 参考
https://www.xmodulo.com/how-to-set-up-vpn-over-ssh-in-linux.html#:~:text=How%20to%20set%20up%20VPN%20over%20SSH%20in,run%20the%20following%20command.%20...%203%20Support%20Xmodulo
[sed] 複数行にまたがったSQLクエリをコメントアウトしたい

# 動機

pg_dumpでダンプしたバックアップファイル(=クエリの連続)の中で以下のDDLのクエリをコメントアウトしたい。

- `CREATE SERVER ~`
- `CREATE FOREIGN TABLE ~`

難しいのは、クエリが改行されて複数行にまたぐことがあること。

# やり方

## `CREATE SERVER ~` をコメントアウト

```queries.sql
CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');

CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw 
    OPTIONS (host 'foo', dbname 'foodb', port '5432');

```

- $ `cat queries.sql | sed -e "/^CREATE SERVER.*;$/ s|^|-- |; /^CREATE SERVER/, /.*;$/ s|^|-- |"`
- $ `cat queries.sql | sed -e "/^CREATE SERVER.*;$/ s|^|-- |" | sed -e "/^CREATE SERVER/, /.*;$/ s|^|-- |"`

のいずれかにより、


```sql
-- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');

-- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw 
--     OPTIONS (host 'foo', dbname 'foodb', port '5432');

```


## `CREATE FOREIGN TABLE ~` をコメントアウト


```queries.sql
CREATE FOREIGN TABLE films (code char(5) NOT NULL, title varchar(40) NOT NULL) SERVER film_server;

CREATE FOREIGN TABLE films (
    code        char(5) NOT NULL,
    title       varchar(40) NOT NULL,
    did         integer NOT NULL,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute
)
SERVER film_server;

```

- $ `cat queries.sql | sed -e "/^CREATE FOREIGN TABLE.*;$/ s|^|-- |; /^CREATE FOREIGN TABLE/, /.*;$/ s|^|-- |"`
- $ `cat queries.sql | sed -e "/^CREATE FOREIGN TABLE.*;$/ s|^|-- |" | sed -e "/^CREATE FOREIGN TABLE/, /.*;$/ s|^|-- |"`

のいずれかにより、

```sql
-- CREATE FOREIGN TABLE films (code char(5) NOT NULL, title varchar(40) NOT NULL) SERVER film_server;

-- CREATE FOREIGN TABLE films (
--     code        char(5) NOT NULL,
--     title       varchar(40) NOT NULL,
--     did         integer NOT NULL,
--     date_prod   date,
--     kind        varchar(10),
--     len         interval hour to minute
-- )
-- SERVER film_server;

```

## これらを一気に行うには、

```bash
function sed_exp(){
  echo "/^$1.*;$/ s|^|-- |; /^$1/, /.*;$/ s|^|-- |"
}

cat queries.sql | sed -e "$(sed_exp "CREATE SERVER")" | sed -e "$(sed_exp "CREATE FOREIGN TABLE")"
```

これにより、

```sql
-- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');

-- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');

-- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw 
--     OPTIONS (host 'foo', dbname 'foodb', port '5432');

-- CREATE FOREIGN TABLE films (code char(5) NOT NULL, title varchar(40) NOT NULL) SERVER film_server;

-- CREATE FOREIGN TABLE films (
--     code        char(5) NOT NULL,
--     title       varchar(40) NOT NULL,
--     did         integer NOT NULL,
--     date_prod   date,
--     kind        varchar(10),
--     len         interval hour to minute
-- )
-- SERVER film_server;

```


# 限界

以上のやり方だと、以下の事象が発生する。

- 文末の`;`の後にコメントがある場合にその続きもコメントアウトされてしまう。
- 文末の`;`の後にコメントがあり、その最後の一文字が`;`の場合にそこまでしかコメントアウトされない。

しかし、`pg_dump`により生成されたファイルに関してこれらは起こらなそうなので一旦よしとする。


## 参考

https://stackoverflow.com/a/30646076

のやり方をそのまま使った。
Windows更新したらデータベース（MySQL）に接続できない！「Cannot connect to server on host 'localhost': 対象のコンピューターによって拒否されたため、接続できませんでした。. Socket Error Code: 10061($274D)」

:::note alert
Windowsに更新したら、データベース開けねぇーーーと思ったそこのあなた！自分もそうなったので1つの解決策としてここに記しておきます。
:::

#エラーの内容「Cannot connect to server on host 'localhost':
対象のコンピューターによって拒否されたため、接続できませんでした。.
Socket Error Code: 10061($274D)」

#解決策
　下のタスクバーの検索で「サービス」と検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3484022/d52623fb-4383-58df-4f2b-5d7e56e8ff3e.png)

そうすると、以下のように「サービス」が出てくるのでそれをクリック！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3484022/4a040d22-f2d7-cd51-6014-e0f519515b2c.png)

表の中から「MySQL」を探して「サービス開始」を押してください。そうすればデータベースが使えるようになっているはずです。また、同じことが起きないように「MySQL」をダブルクリックして、スタートアップの状態を「手動から自動」にしておくと良いかも！
dangerouslySetInnerHTMLを使ってHTMLを描画しようとするとHydration failedになる

# はじめに

Reactの`dangerouslySetInnerHTML`とは、生のHTMLをReactコンポーネントに直接注入するためのプロパティです。

例)
```tsx: index.tsx
const content = '<a href="https://www.google.com" target="_blank">クリック</a>'

return (
  <div dangerouslySetInnerHTML={{ __html: content }}></div>
)
```


このプロパティを使ってしばしば外部コンテンツを表示したり、
ユーザーの入力をプレビューしたりすることがあります。

> そう、丁度こんな風に
> 
> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/555c9325-a58c-35db-d4c6-15adad92db9c.png" />

そんな`dangerouslySetInnerHTML`を初回描画時に読み込むと`Hydration failed`のエラーになりましたので、その解決方法です。

## 原因

`Hydration failed`とは、
**サーバーサイド**でレンダリングされたHTMLと、
**クライアントサイド**でレンダリングされたHTMLとの間で
不一致があるときに発生します。

つまり最初の例の場合、

```html: サーバーサイド
<div></div>
```

```html: クライアントサイド
<div>
  <a href="https://www.google.com" target="_blank">クリック</a>
</div>
```

のように差が発生しているためエラーになっていました。


## 対処法

### `useEffect`を使う

useEffectを使い、最初のレンダリング後に表示したいものを表示する

```tsx: index.tsx
const [content, setContent] = useState()

useEffect(() => {
  setContent('<a href="https://www.google.com" target="_blank">クリック</a>')
}, [])

return (
  <div dangerouslySetInnerHTML={{ __html: content }}></div>
)
```

こうすることで、サーバーサイドとクライアントサイドのレンダリングの差異を無くすことができました
【python】http.client の使い方・注意点集

# http.client使用時の注意点
http.clientを利用したget/postなどのAPI通信を記述する際に少し癖があったので
注意点について自分用の備忘録となります。
authorization エラーや
InvalidURL エラー
gai error(getaddrinfo)エラーなど解決できるかも。

# 注意点解説
## 1. "https://" 等は書かない
アクセスしたいエンドポイントが
"https://OOO.jp.~~.com/api/v2/user/XXXXX"
の場合
```python
path = "OOO.jp.~~~.com"
conn = http.client.HTTPSConnection(path)
```
となる。
path内の "https://" は省略する必要がある。
また３.にて後述するがorigin以降は別途記述する必要がある。

## 2. httpsやhttpに応じてConnection名を変える
アクセスしたいエンドポイントが
"https://OOO.jp.~~.com/api/v2/user/XXXXX"
の場合
```python
path = "OOO.jp.~~~.com"
#アクセスしたいエンドポイントがhttpsの場合
"https://OOO.jp.~~.com/api/v2/user/XXXXX"
conn = http.client.HTTPSConnection(path)

#アクセスしたいエンドポイントがhttpの場合
"http://OOO.jp.~~.com/api/v2/user/XXXXX"
conn = http.client.HTTPConnection(path)

```
となる。
conn 部分の http.client. 以降は
httpの場合　：HTTPConnction(path)
httpsの場合：HTTPSConnection(path)
とする。

## 3.origin部分とそれ以降で分けて記述する
originとは
"http://example.com/app1/index.html"
で言うところの
"http://example.com"
の部分。
同一ページにおけるURLの共通部分（不変の部分）くらいのイメージ。
詳しくは検索したり以下のサイトなど参照。
https://developer.mozilla.org/ja/docs/Glossary/Origin

今回
アクセスしたいエンドポイントが以下の場合
"https://OOO.jp.~~.com/api/v2/user/XXXXX"
originは
"https://OOO.jp.~~.com" であり
この部分と
"/api/v2/user/XXXXX"
以降の部分は分けて記述する必要がある。


どのように記述するかは以下に例を記載する。
```python
# ここのpathはhttps://などを省くかつorigin部分のみを記述する
path = "OOO.jp.~~~.com"
conn = http.client.HTTPSConnection(path)

# request送信
# ここの第二引数にorigin以降のAPIエンドポイントURIを記載する。
conn.request("GET","/api/v2/user/XXXXX")
```

## 4.結果のデコードについて
データは最初
http.client.HTTPResponse型で返ってくる
以降
http.client.HTTPResponse型
→byte型
→str型
→辞書型
で変更していく。
（どのように変更してもいいが一例として記載しておく程度に留める）
```python
import ast

#内容取得 type：http.client.HTTPResponse型
res = conn.getresponse()

#内容のbyte型への変換 type:byte型
data = res.read()

#内容のbite型→文字列型へのデコード type:str型
res = data.decode("utf-8")

#文字列型→辞書型への変換 type:dictionary型
dict_ = ast.literal_eval(res)
```


# 完成版のコード
例）https://OOO.jp.~~.com/api/v2/user/XXXXX
といったエンドポイントにget接続する場合


```python:http_client.py
import ast
import http.client

# ここのpathはhttps://などを省くまたorigin部分のみを記載
path = "OOO.jp.~~~.com"
# http or httpsによってclient以降の記述を変える。
conn = http.client.HTTPSConnection(path)

# header設定 必要に応じて
access_token = "OOO"
headers = { 'authorization': access_token }


# request送信
# ここの第二引数にorigin以降のAPIエンドポイントURIを記載する。
conn.request("GET","/api/v2/user/XXXXX" , headers=headers)


# 内容取得 type：http.client.HTTPResponse型
res = conn.getresponse()

# 内容のbite型への変換 type:byte型
data = res.read()

# 内容のbyte型→文字列型へのデコード type:str型
res = data.decode("utf-8")

# 文字列型→辞書型への変換 type:dictionary型
dict_ = ast.literal_eval(res)


print(dict_)
```
form_tag要素の発火を細かく指定するには

## 環境
Rails5.2
Vagrant + Ubuntu20.4
JavaScript

## 内容
ファイルを選択するダイアログ画面を表示するとき、Viewの中で下記のような記述をします。idは、form_tag、file_field_tag、submit_tagの各要素にそれぞれ設定することができます。

```index.html.slim
.form-group.row
    = form_tag xxxxx_path, multipart: true, class: 'mb-3', id: 'tag_form' do 
        = file_field_tag :file, id: 'tag_file'
        = submit_tag "実行", class: 'btn btn-primary', id: 'tag_submit'
```

上記のコードを動かすと下記のように表示されます。
![スクリーンショット 2023-06-19 131846.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/badeb927-0761-162e-6500-ce4826084c44.png)

３種類の要素でそれぞれクリックイベントを発火させた場合、どこのロジックに入ってくるか確認してみます。
```xxxxx.js
document.addEventListener('turbolinks:load', function() {
    document.getElementById('tag_form').addEventListener('click', function() {
        alert("クリック１");
    })

    document.getElementById('tag_file').addEventListener('click', function() {
        alert("クリック２");
    })

    document.getElementById('tag_submit').addEventListener('click', function() {
        alert("クリック３");
    })
}, false)
```

《ファイルを選択》をクリックすると、下記の２つのアラートが表示されます。つまり、file_field_tag要素、form_tag要素の順番でイベントが発火しています。

![ファイルクリック1回目.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/4cdd7952-55ca-5eca-f80d-1690fc7efd36.png)

![ファイルクリック2回目.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/aaa30b7d-74ff-909a-e5c9-f378835991ae.png)

《実行》をクリックすると、下記の２つのアラートが表示されます。つまり、submit_tag要素、form_tag要素の順番でイベントが発火しています。

![実行クリック1回目.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/b6cfa1eb-ea91-bd19-084d-e253ce831863.png)

![実行クリック2回目.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/239287b3-3fea-a9e8-4897-2a40c97037f6.png)

### まとめ

- 《ファイルを選択》、《実行》のどちらをクリックしても、同じイベントを発火させたい場合は、form_tag要素のidを指定します。

- 《ファイルを選択》をクリックしたときだけイベントを発火させたい場合は、file_field_tag要素のidを指定します。

- 《実行》をクリックしたときだけイベントを発火させたい場合は、submit_tag要素のidを指定します。
python maya fps

```python
import maya.cmds
import maya.mel as mel

def SetFPS(fps):
    unit = 'film'
    if fps == 15:
        unit = 'game'
    elif fps == 24:
        unit = 'film'
    elif fps == 25:
        unit = 'pal'
    elif fps == 30:
        unit = 'ntsc'
    elif fps == 48:
        unit = 'show'
    elif fps == 50:
        unit = 'palf'
    elif fps == 60:
        unit = 'ntscf'
    else:
        unit = str(fps)+'fps'

    cmds.currentUnit( time=unit )
    fps = mel.eval('currentTimeUnitToFPS')

SetFPS(24)
```