OmniAICreator/Qiita-1.07M huggingface dataset:
GO言語のgo-echartsパッケージを使って時系列チャートを出力する方法

# はじめに

GO言語で開発したプログラムから、ちょっとしたチャート（グラフ）を出力する時にgo-echartsというパッケージが便利です。

https://github.com/go-echarts/go-echarts

しかし、サンプルプログラムが少ないのでグラフを出力するためのデータ構造がわかりにくいです。そこで、誰かのためになればと思って解説の記事を書いてみました。

# go-echartsについて

go-echartsは、JavaScriptのチャートライブラリApache ECharts

https://echarts.apache.org/en/index.html

を使ったインターラクティブなチャートを含むHTMLファイルをGO言語から出力するものです。このライブラリのサンプル

https://echarts.apache.org/examples/en/index.html

にあるようなチャートの一部を出力できます。チャートを出力するための設定やデータは、JavaScript版と同じように考えればよいと思います。

go-echartsのサンプルは、

https://github.com/go-echarts/examples

にありますが、自分でビルドして起動しないと表示が見られませんし、解説もありません。


# 時系列チャートの出力

時系列チャートは、

```go
package main

import (
	"math/rand"
	"os"
	"time"

	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
)

// 時系列のランダムデータ作成
func generateTimeLineItems() []opts.LineData {
	items := []opts.LineData{}
	t := time.Now().UnixMilli()
	for i := 0; i < 100; i++ {
		items = append(items, opts.LineData{Value: []interface{}{t + int64(i*1000), rand.Float64()}})
	}
	return items
}

func main() {
	line := charts.NewLine()
	line.SetGlobalOptions(
		charts.WithXAxisOpts(opts.XAxis{Name: "time", Type: "time"}, 0),
		charts.WithTitleOpts(opts.Title{Title: "Time line example", Subtitle: "This is the subtitle."}),
	)

	line.SetXAxis(nil).AddSeries("Time Chart", generateTimeLineItems())

	f, _ := os.Create("time_line.html")
	line.Render(f)
}
```

のプログラムで出力できます。結果は、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874862/9197a57f-29a4-55d3-41ef-721d7b08a4b3.png)

のようになります。

時系列チャートを出力する時のポイントは、３つです。


### X軸を種別を"time"にする

```go
charts.WithXAxisOpts(opts.XAxis{Name: "time", Type: "time"}, 0),
```

### X軸のデータをnilにする

```go
line.SetXAxis(nil)
```

### シリーズのデータを２次元のスライスにする

> [mSecのUNIXタイム,Y軸の値]....

```go
t := time.Now().UnixMilli()
for i := 0; i < 100; i++ {
		items = append(items, opts.LineData{Value: []interface{}{t + int64(i*1000), rand.Float64()}})
}
```

ということです。

Lineだけでなく他の種類のチャート（グラフ）にも応用できます。

## この解説を書いた理由

この記事は、

https://note.com/twsnmp/n/nd90978e728c0

を開発している時に調べたことを元にしています。
ChatGPTでごみ収集カレンダーの画像から予定表に追加する

https://qiita.com/usagikobo2024/items/bfa43b6a2e44b9cd8d25

↑前回GASでスプレッドシートからGoogleカレンダーに予定を追加する方法を書きました。

そもそもがごみ収集カレンダーをGoogleカレンダーに取り込みたかったので、今回はChatGPTを使って画像から予定を抜き出してみます。

1.画像を準備します。
都市名＆ごみ収集カレンダーでだいたい出てきます。(今回は適当に選びました)
都合よく凡例も側にあったので画像は１枚で済みました。凡例を別に取り込んでも大丈夫だと思います。
![スクリーンショット 0006-09-16 10.49.34 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/50d6e88c-e865-7c5f-4f1d-e3bd6041259b.png)

2.ChatGPTに画像を添付します。
![スクリーンショット 0006-09-16 10.48.19 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/3434d18c-f662-e2d7-215a-46258ad8002a.png)

3.プロンプトをこのように書き込むときちんとテーブルにしてくれました！
　※ただ、枠線が薄いからか、かなり間違っています。
![スクリーンショット 0006-09-16 11.04.14 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/112f83e4-6e86-a376-181c-6b6937d5c5d0.png)

4.そのままテーブルを選択して、スプレッドシートにコピーします。
![スクリーンショット 0006-09-16 10.55.01 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/959ecf5e-be18-75ea-0b4f-0728d09c70e0.png)

あとは前回作ったGASでGoogleカレンダーに取り込めばOKですね。
※GASを動作させる前に、取り込んだ内容が正しいかは要確認です。

https://qiita.com/usagikobo2024/items/bfa43b6a2e44b9cd8d25


1年に一回やっておけば、アプリや紙のカレンダー見なくてもGoogleカレンダーで一括管理できるのでおすすめです。
私はAlexaに朝の目覚ましとその日の予定をお知らせしてもらっているので、今日は何のゴミの日かなと確認しなくて済んでいます。
OpenSearch Serverless概要


# はじめに

BedrockでRAGの構成を実現する際のベクトルDBとしてOpenSearch Serverlessが選択肢としてあります。あまり詳細把握できていなかったので簡単にですがまとめました。prefixは`aoss:`というものになります。


# OpenSearch Service

- OpenSearch
    - コミュニティ主導・Apache2.0ライセンスのオープンソース検索・分析スイート
    - データストア、検索エンジンの`OpenSearch`、可視化、UIツールの`OpenSearch Dashboards`から構成
    - セキュリティ、パフォーマンス分析、機械学習など様々なプラグインによる機能拡張が可能
    - ユースケース
        - 全文検索
        - ログ分析


- ドキュメント
    - JSON形式で表現される単一のデータ
    - 一般的なDBのレコードに相当

- OpenSearchの課題
    - 設計時の課題
        - データサイズ、シャード数、リクエストレートなどをベースにクラスターのサイジングやインデックスの設計が必要
    - 運用時の課題
        - ストレージはノードに紐づいているため、格納データの増加に伴ってノードの増強・増設が必要
        - スケールはB/Gデプロイを伴うため実行コストが高い

# OpenSearch Serverless

- Serverlessアーキテクチャによる運用の改善
    - インでキシング、検索、ストレージワークロードを分離。インデキシングと検索ノードは複数のAZに分散配置され、トラフィックに応じて個別にスケール
    - 実データはS3に格納されるが、検索パフォーマンスを高めるためにノードのボリュームにもキャッシュされる
    - インデックス内のデータのライフサイクルは、サービス側で管理されている


## Collection

- 概要
    - OpenSearch Serviceにおけるドメイン(クラスター)に相当する、論理的なインデックスの集合
    - API、ダッシュボードエンドポイントはコレクションごとに発行される
    - 種類
        - SEARCH collection
            - コンテンツ検索や商品検索などでの利用を想定
            - ほぼ全てのデータはノードにアタッチされたホットストレージに格納
        - TIME-SERIES collection
            - モニタリングやリアルタイム分析等で大規模なデータを扱うケースを想定
    - 利用可能なクライアント
        - OpenSearch Client Library
        - Logstash
        - Fluentd
        - Fluletbit
        - Firehose


## OpenSearch Dashboards

- 概要
    - 可視化、ログ検索用途で利用可能
    - Discover, Dashboard, Visualize, DevToolsの基本機能提供のみ


## OCU(OpenSearch Capacity Unit)キャパシティ管理

- 概要
    - 検索とインデキシングそれぞれの処理に対して、個別に割り当てられるキャパシティユニット
    - 1 OCU (1 vCPU, 6GiB RAM, 120 GiB Disk)
    - 最初のコレクションが作成されると、アカウントごと(厳密にはリージョンごと)に、検索とインデキシングに2OCUずつ、計4OCUが割り当てられる
    - 処理要求や負荷の増加に応じて自動的にOCUが増加する (スケールアウト)
    - ワークロードの有無にかかわらず、割り当てOCUに応じた時間課金となる
    - OCUは同一リージョンであれば複数コレクション間で共有


## セキュリティ

- 認証
    - ダッシュボードへのログインは、IAMユーザーの認証情報、もしくはSAMLを利用可能。複数のIdPを利用可能
    - APIアクセスでは、IAMユーザーもしくはIAMロールのクレデンシャルが必要

- `データアクセスポリシー`
    - IAMユーザー、IAMロール、SAML認証時のユーザーIDまたはグループに対してコレクション・インデックスに対する操作を許可するポリシー
    - 操作の許可単位はパーミッションと呼ばれる権限セットに準ずる
    - クロスアカウントアクセスは未サポート

- `ネットワークポリシー`
    - コレクションへのアクセス経路を制御する機能。単一のポリシーを複数コレクションに一括適用することも可能
    - ポリシータイプ
        - Public
        - VPC
    - アクセス制御の対象
        - APIエンドポイント
        - OpenSearch Dashboards

## バックアップ

- 現状ではスナップショットがサポートされていないため、論理的なバックアップ&リストアの仕組みが必要
- Logstash等の対応クライアントでServerlessからデータを抽出し、S3に配置するといったアプローチが考えられるが、実際のところコレクションからのデータ取得はコストと時間がかかる
- 検索アプリケーションであればS3やDBなどにマスターデータを保管しておく、時系列データ分析であればOpenSearch Serverlessコレクションとは別のデータストアに並行でデータを保管しておくなどの対処が現実的

## 制約

- クロスアカウントアクセスは未サポート
- 別アカウントの環境からアクセスする際は、リソース保持アカウントの認証保持を使用する必要あり
- また、Publicアクセスがネットワークポリシーで許可されている必要あり
- インデックスのリフレッシュ間隔は、リクエストサイズに応じて10-30sとなる。登録したドキュメントが即検索可能となることが要求されるユースケースは不向き


https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AmazonOpenSearchServerless_0131_v1.pdf
IPアドレスでのアクセス制限などを回避できる(可能性がある)脆弱性がhttpdにあった

# 概要
[CVE-2024-38474](https://nvd.nist.gov/vuln/detail/CVE-2024-38474)がなかなか厳しい内容だったのでメモ。[公式](https://httpd.apache.org/security/vulnerabilities_24.html)では具体的な攻撃シナリオが見えなかったので完全に油断してた…

この脆弱性の詳細については[報告者のブログ](https://blog.orange.tw/posts/2024-08-confusion-attacks-en/)を見て貰うとして、その中で特に気になる部分があったので確認した。

以下はRHEL 8互換OSで確認した。

# 影響を受ける状況
httpd 2.4.60未満の全てのバージョンでmod_rewrite/mod_proxyを利用している特に影響を受ける様子。

特に気になった部分というのが、mod_proxyが対象になっているという点。**「Reverse Proxyでも構築してなければ関係なくね？」** と高をくくっていたが、あれ？と思ったのがここ。

```/etc/httpd/conf.d/php.conf
...
<FilesMatch \.(php|phar)$>
    SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
...
```

うん、入ってますね、proxyの文字が。

# 脆弱性を試用した攻撃シナリオ
対策前のバージョンのhttpdが入ったサーバがあったのでこれで確認してみる。

```bash
# yum list installed httpd
インストール済みパッケージ
httpd.x86_64          2.4.37-62.module+el8.9.0+1798+d36b471e          @appstream
```

よくあるハックで **「WordPressに不正にログインできないようにするため、特定のIPアドレス以外からのログイン画面へのアクセスを禁止する」** というのがあると思っている。一般的かどうかは不明だが、近隣では金科玉条のように言われている。

そこでこのような設定を記述してみた。

```/etc/httpd/conf.d/wp.conf
<Files wp-login.php>
  Require local
</Files>
```

これなら誰も？WordPressにログイン出来ないはず。実際に別のIPを持つPCのブラウザで普通にアクセスしてみると403エラーが出た。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63695/30dce656-eac9-216d-e981-26fb8becc95e.png)

CVE-2024-38475ではURIに与えられた **%3f** を **?** と誤認してhttpdとproxyの先で異なる処理をしてしまうことが問題らしい。

そこで前掲のブログにあったように次のような細工をしてみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63695/df1a600d-3604-ce91-373d-9c4c93365601.png)

WordPressでサイトを公開する際に、index.phpへのアクセス制限はしないだろうということで、設定したらログイン画面が表示出来てしまった…

ちなみにAllowOverride AllでWordPressデフォルトのRewrite設定を有効にした場合は、このURLではログイン画面にはアクセス出来なかった。

# まとめ
* ミドルウェアにある未知の脆弱性の影響を受ける可能性があるので、IPアドレスでのアクセス制限を過信するのはやめよう。
* WordPressは自動アップデートを利用するとして、ミドルウェアも自動または速やかにアップデートするようにしよう。
* CVE情報を細かくチェックしよう。速報だけで無くNVDの評価のタイミングで再チェックする。（大事）
paizaラーニング問題集「階段の上り方2」を解いてみた

▼感想：

問題文に
「n, a, b の値によっては答えが0になることがある」
と書かれていたので、
0になる場合を考えif,elifでコーディングしました。
しかし、解答コード例はそうコーディングされていませんでした。
もう少し、理解を深めたいと思います。

▼コード：
```py
########## 処理0(準備) インプット,リスト定義など ###########

n,a,b = map(int,input().split())

########## 処理1 漸化式の定義、計算、出力 ##########

if a > n and b > n:

    print(0)
    
elif a > n:

    if n % b != 0:
        print(0)
    elif n % b == 0:
        print(1)

elif b > n:

    if n % a != 0:
        print(0)
    elif n % a == 0:
        print(1)

else:

    dp = [0]*(40+1)
    
    dp[0] = 1
    
    for i in range(1,n+1):
        if i >= a:
            dp[i] += dp[i-a]
        if i >= b:
            dp[i] += dp[i-b]

    print(dp[n])
```
wslでmanim その26

# 概要
wslでmanimやってみた。
練習問題やってみた。

# 練習問題
ハノイの塔を図解せよ。

# 方針
- 再帰を使う。

# 成果物

![test_ManimCE_v0.18.1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/0bb17b27-19f8-f9e8-4df2-ccb9d7a7bee4.gif)

# サンプルコード

```


from manim import *

class test(Scene):
	def hanoi(self, disk: int, src: str, dest: str, support: str):
		if disk < 1:
			return
		self.hanoi(disk - 1, src, support, dest)
		print(f'move {disk} from {src} to {dest}')
		self.move(disk, src, dest)
		self.hanoi(disk - 1, support, dest, src)
	def move(self, disk, src, dest):
		v = 0
		di = self.disk1
		point1 = np.array([-3, -3, 0])
		point2 = np.array([-3, -3, 0])
		if disk == 1:
			di = self.disk1
		elif disk == 2:
			di = self.disk2
		elif disk == 3:
			di = self.disk3
		if src == 'A':
			point1 = np.array([-3, 1, 0])
			self.a = self.a - 1
		elif src == 'B':
			point1 = np.array([1, 1, 0])
			self.b = self.b - 1
		elif src == 'C':
			point1 = np.array([5, 1, 0])
			self.c = self.c - 1
		di.move_to(point1)
		self.play(Write(di))
		self.wait()
		if dest == 'A':
			point2 = np.array([-3, -3, 0])
			di.move_to(point2 + UP * 4)
			self.a = self.a + 1
			v = self.a
		elif dest == 'B':
			point2 = np.array([1, -3, 0])
			di.move_to(point2 + UP * 4)
			self.b = self.b + 1
			v = self.b
		elif dest == 'C':
			point2 = np.array([5, -3, 0])
			di.move_to(point2 + UP * 4)
			self.c = self.c + 1
			v = self.c
		self.play(Write(di))
		self.wait()
		di.move_to(point2 + UP * (v - 1) * 0.6)
		self.play(Write(di))
		self.wait()
	def construct(self):
		self.a = 3
		self.b = 0
		self.c = 0
		self.disk1 = Rectangle(width = 1, height = 0.5, color = BLUE)
		self.disk2 = Rectangle(width = 2, height = 0.5, color = GREEN)
		self.disk3 = Rectangle(width = 3, height = 0.5, color = RED)
		rod1 = Line(start = [-3, 0, 0], end = [-3, 4, 0])
		rod2 = Line(start = [0, 0, 0], end = [0, 4, 0])
		rod3 = Line(start = [3, 0, 0], end = [3, 4, 0])
		start_pos = np.array([-3, -3, 0])
		self.disk1.move_to(start_pos + UP * 1.2)
		self.disk2.move_to(start_pos + UP * 0.6)
		self.disk3.move_to(start_pos)
		rod1.move_to(start_pos + UP * 1.6)
		rod2.move_to(start_pos + RIGHT * 4 + UP * 1.6)
		rod3.move_to(start_pos + RIGHT * 8 + UP * 1.6)
		self.add(self.disk1, self.disk2, self.disk3, rod1, rod2, rod3)
		self.hanoi(3, "A", "C", "B")
		self.wait()





```

以上。
マイジオ（MyGeo）を作ってみた。（Youは、自分が作ったGeoデータをどうやって外出先のスマホ・タブレットで見る？）

【マイジオ】
https://office-shirado.com/mygeo/

「Youは、自分が作ったGeoデータをどうやって外出先のスマホ・タブレットで見る？」

そんな素朴な疑問を自分に投げかけ、「あっないかもｗ」ということで、新しいＷｅｂ地図を作成しています。
![画面イメージ01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67836/534a9cd2-bdfa-11c3-6ee8-a199bda299cb.png)
![画面イメージ02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67836/1eef94b8-abeb-63ca-263b-5693583a406d.png)

いつも通り、「自分が使うＷｅｂ地図」を目指しています。
Ｗｅｂ地図（ＷｅｂＧＩＳ）のベースは、MapLibreで、データはMySQLのテーブルに入れています。

技術的なことを簡単に言うと、ローカルファイル（SIMA・GeoJSON）を読込み、JavaScriptで点・線・面ごとのFeatureに分類し、Featureのまま配列としてMySQLのテーブルに入れています。

データの中心座標及びズームレベル１３のタイル座標を別途保存していることにより、画面表示外のＧｅｏＪＳＯＮ（Feature）は読み込まないため、いきなり全データを読み込むことはなく、必要な部分だけインポートします。
※細かい仕様は変更の可能性あり。

![スクリーンショット 2024-09-16 11.07.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67836/51679088-cd31-cf56-f676-93cb7e976266.png)

ユーザーのデータは動的であるため、都度ベクトルタイルを作るのは、利用しているレンタルサーバーでは難しいと思いますし、私自身がそのスキルがないということで、この手法にしました。

現在は、データをインポート・登録できるというだけですが、基礎的なところは実装できたと思うので、今後細かいところを調整してサブスクサービス（月５００円程度）を目指しています。

現時点では、料金設定なども未定ですので、無料でお使いいただけます。お気軽にユーザー登録いただき、データを入れて外出先で見て頂ければと思います。
※システム変更に伴う仕様変更があるかと思いますので、ご了承願います。

「Youは、自分が作ったGeoデータをどうやって外出先のスマホ・タブレットで見る？」
→私はマイジオ（MyGeo）！！！
005 皆で使おう　専門分野特化のＬＬＭ　streamlitでインターネットで公開！！！



皆で使おう　専門分野特化のＬＬＭ　streamlitでインターネットで公開！！！

追加学習済みのＬＬＭを安価に共有する方法です。
完成ずみのwebapiを使って、
既にある、皆様のポータルサイトで呼び出したり
別途、独立サイトで運用したり　色々できます。

完成イメージ


![スクリーンショット 2024-09-16 9.50.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237930/8ffcd30c-ae6b-db88-d961-705ae36c5a38.png)






https://bono0-ghc4uieygq-an.a.run.app

https://
bono0-ghc4uieygq-an.a.run.app


前回までの２４


第四回：追加学習済み　LLMのwebapiをクラウドデプロイ

https://qiita.com/bono0/items/f954ca48d19cc51fb344


第三回：追加学習済み　LLMのwebapiを作る

https://qiita.com/bono0/items/f3330d8a57024e7cd44b



第二回：大規模言語モデルの簡単な追加学習、実践編

https://qiita.com/bono0/items/bd0a01e50d5804bdacf3



第一回：大規模言語モデルの簡単な追加学習

https://qiita.com/bono0/items/4a992dc388527d3b99f4





webアプリの作り方は　多々あります。
今回は、AI屋さんならではの　超簡単streamlitで　デプロイです。

内容的には、実質一行のみ
第一回目のhttpリクエストと同じです。



![スクリーンショット 2024-09-16 14.41.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237930/63150dfc-6bc3-8e88-f4bf-bd70446415a9.png)




あとは、デプロイのみです。
上記は、

　クラウドのサーバーレスに　デプロイしてみましたが、
　他にも、サーバーあり（≒普通の仮想マシン）や
　特定業者のSaaSや 
　物理マシンなど
 
色々選べます。
既存の環境があって相乗りできればお安く済みますし
１０年運用が想定されれば、物理マシンも良いかと思います。
マシンが一つあれば、稼働率の低いであろう
　専門分野特化のＬＬＭ　＊　n個
などのサービスにはもってこいです。

更に、ローカルLLMなら完全独立できます。
企業さんによっては、データーを内部に維持するために
ローカルLLM　マストのケースもあると思います。
その場合、当然webapiもwebアプリも　ローカル物理マシンです。

サーバーレスは、特定の業者の特定のサービスに大きく依存します。
何処かの誰かが勝手に作ったサービスが
数年後に、維持されていない可能性を考慮すると、
忘れた頃に移植作業する羽目になるくらいならば、
初めから物理マシン　も魅力的です。





無事に
　皆で使おう　専門分野特化のＬＬＭ　streamlitでインターネットで公開！！！
今回も完成です。おめでとうございます。

次回は、flaskでhtml編ですかね。

ー続くー
paizaラーニング問題集「階段の上り方1」を解いてみた

▼感想：

ヒントを参考にして解きました。
理解を深めるため、
実際にn=0から1ずつインクリメントして、結果を見たいと思います。

▼コード：
```py
########## 処理0(準備) インプット,リスト定義など ###########

n = int(input())

########## 処理1 漸化式の定義、計算、出力 ##########

dp = [0]*(40+1)

dp[0] = 1

for i in range(1,n+1):
    
    if i >= 1:
        dp[i] += dp[i-1]
    
    if i >= 2:
        dp[i] += dp[i-2]

print(dp[n])
```
[ODC]参照するLibraryのバージョンを過去のものに戻す

2024/09/15時点の情報に基づく。将来のどこかで、Libraryを参照するときに任意のバージョンを指定できるようになったら、以下の方法は不要になる。

# 環境情報
ODC Studio(Version 1.4.23)

# Libraryの基礎
## RevisionとVersion
LibraryをPublishすると、新しいRevisionが作成される。
このRevisionは他の開発者が参照することはできない（テスト用Appを指定して、そのAppだけ参照することはできる）。

テストが終わったら、Revisionを選択して「Release Library」する。
このときに新しいVersionが作成される。このVersionは他の開発者も参照できる。

OutSystems UIもLibrary。OutSystems UIを例にバージョン履歴ページを示す。青い丸で囲った部分がそれぞれVersionとRevision番号を表している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/e82b6cd8-e59c-5451-e17f-32c3007602f9.png)

## 参照しているLibraryがRelease Libraryされたら
AppをODC Studioで開くと、メニューバーで赤線を引いたReview library updatesアイコン右上に●が表示されることがある。これは、参照しているLibraryにより新しいVersionがあることを示す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/625974a7-4398-6257-d557-9bf1eb99f430.png)

以下のように、更新があったLibraryの一覧（名前、新しいVersionとそのRelease日、Release notes）が表示される。ここでUpdateボタンをクリックすると、参照するVersionが「最新のもの」になる（特定の古いVersionにはできない）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/cd09872c-73a1-b50e-5711-ff4625ada886.png)

## 現在参照しているVersionを確認する方法
ODC Studioで参照しているLibraryを選択すると、プロパティウィンドウのVersion項目で番号を確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/645d0fc2-4e4c-12e9-8946-18a3cba8d411.png)

（ODC Portal上で、AppのProducersタブを開くことでも確認できそうなものだが、この記事を書いている時点では、その場所には今参照しているVersionではなく最新Versionが表示された）

# 参照LibraryのVersionを、Appの古いRevisionで参照していたものに戻す手順
以下のように、Version 2.19.2のOutSystems UIを参照している。これを1つ前のVersion 2.19.1参照に戻したい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/80e72de5-2145-d0d0-89dd-ec5b8e086a32.png)

ODC Studioで、左上のハンバーガーメニュー > App > Compare and merge with another revision or file...を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/7bcbe845-d14e-8735-84fa-c712254bf67c.png)

戻したい先のVersionを参照している、過去のRevisionを選択してOKボタンをクリック。

Dependenciesの下に、対象のLibraryがある。
The other version側でこれを選択し、Versionが希望のものであることを確認したら、チェックボックスにチェックを入れてMergeボタンをクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/03ebe3e7-6b9c-e994-d6cf-3c6ffb3e9473.png)

これで参照するVersionが古いものに戻ったのでPublishすればよい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/4dbad680-0f5c-5f80-7b2c-c59ed59b07c8.png)
ヒューマノイドのウェイトペイントのやり方と、頂点ごとにウェイトの値を手入力で調整する方法

こちらの動画がかなりわかりやすかったので、自分用の備忘録としてメモ
https://youtu.be/V__4FhuOcec?si=9-GXbLlLjhgxi1Kx

# ウェイトペイントのやり方

## ボーンとオブジェクトの関連付け
まず「最初に」関連付けたいオブジェクトを全部選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/dfaa8c3f-cdcb-b22e-8bb8-c3494b1e860c.png)

「オブジェクトを選択した後に」アーマチュアを選択(Shift押しながらボーンをクリック)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/ca84e31a-945a-cc9d-12a6-4390ec8ec115.png)

親→空のグループで(もしくは自動ウェイトで)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/0f3f7e32-3996-554d-c5ed-ea91b6752c09.png)

これでボーンとオブジェクトの関連付けは完了。

関連付けの際「自動ウェイトで」を選んだ場合はblenderでほぼ自動でやってくれるが細かい修正がかなり必要になる。
「空のグループで」を選んだ場合は全部自分で塗る必要があるが、最初から好みの動きを付けられるので自動設定の変な挙動に悩む必要がない。
どちらにするかはお好みだけど、自分みたいな慣れてない人間は「自動ウェイトで」の方が良い気がする。


## ウェイトペイントの行い方
「先に」アーマチュアを選択(ボーンをクリック)し、「その後」にShift押しながらウェイトペイントしたいオブジェクトを選択。
順番を間違えてなければ項目が出てくるのでウェイトペイントを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/9a852b72-0a07-6541-f68c-6552429c77d1.png)

緑の三角(データ)を選択し、頂点グループを選ぶ。
（関連付けを行った時点で、ボーンの名前で頂点グループが作成されているので対応させたいボーンの名前のものを選ぶ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/8750dec1-5c96-260d-07db-3818996e5f48.png)

後はウェイトの値や半径、強さを弄りながらどの程度影響させるかを塗る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/29597362-7594-f38b-6fcd-9f522df82e5f.png)

設定を行ったHeadのボーンを動かすとオブジェクトが影響を受けることを確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/2e8c9116-c810-73f1-cd96-a8ea8c9fdcdd.png)

後はこれをボーンの数だけ繰り返して修正を行っていけばOK。


# (おまけ)頂点ごとにウェイトの値を手入力で調整する方法
## 単一頂点の修正
オブジェクトを選択し、編集モードに変更。
アイテムタブを選んだあと、修正したい頂点を選択すると画像のようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/72bf7686-7ec6-f973-842d-8d25dd5091a3.png)


頂点ウェイトの下の項目の数字を弄ることで点単位でのウェイト編集が可能になる。
項目がない場合は、緑の三角(データ)を選択し、頂点グループを選び頂点グループの下の「割り当て」ボタンを押すと追加が可能。
正規化を押すことで合計値が１になるよう調整してくれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/d7e91fcc-1010-64f5-1042-2c9d14e41ddb.png)

## 複数の頂点を纏めて同じ値に修正したい場合
修正したい頂点を全て選択肢、どれでもいいのでその中の一つを「Shiftを押しながら２回クリックして選択解除→再選択」する。
上記と同じように修正した後「コピー」を押すことで、選択されたすべての頂点に同じ値を適用できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2308792/20fbec0e-f71b-4f31-1f2a-cddabedf52ab.png)
ssh接続時に表示される最終ログイン情報を増やしたい


ssh接続時に出力されるログイン履歴は最終(現在)しか表示されない。

```
Last login: Mon Sep 16 10:19:05 2024 from 192.168.x.xx
```

これの幅を増やしたいなと思ったので、ログイン履歴をlogファイルに書き込み、そこから読み込むようにした


```
# ログファイルのパス
LOGFILE="$HOME/.ssh/.log/all_sshauthlog.log"

# ログディレクトリが存在しない場合は作成
if [ ! -d "$HOME/.ssh/.log" ]; then
    mkdir -p "$HOME/.ssh/.log"
fi

# ログファイルにSSH認証の成功・失敗の履歴を記録
sudo journalctl -u ssh | grep -E "Accepted|Failed password" >> "$LOGFILE"

# カラーコードの設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # 色リセット

# ログイン履歴の最後の3回をフォーマットして表示
echo -e "最後の3回のログイン履歴:\n"
tail -n 3 "$LOGFILE" | while read line; do
    # 日付を抽出
    DATE=$(echo "$line" | awk '{print $1, $2, $3}')
    # 成功か失敗かを判定
    if echo "$line" | grep -q "Accepted"; then
        STATUS="${GREEN}Accepted${NC}"
    else
        STATUS="${RED}Failed${NC}"
    fi
    # IPアドレスを抽出
    IP=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')

    # 結果を表示
    echo -e "${YELLOW}$DATE${NC}"
    echo -e "  Status: $STATUS"
    echo -e "  IP Address: ${YELLOW}$IP${NC}\n"
done
```

これだけだと接続時に実行されないので
.bashrcから上記のスクリプトを呼び出すようにする。

```chmod +x sshAuth.sh```
```vi .bashrc```

```ruby:.bashrc
~/.ssh/sshAuth.sh

```

結果
```
Sep 16 10:19:04
  Status: Accepted
  IP Address: 192.168.3.34

Sep 16 10:25:43
  Status: Accepted
  IP Address: 192.168.3.34

  Sep 16 10:29:20
  Status: Accepted
  IP Address: 192.168.3.34

```
Pythonで『The MagPi』の全号を一括ダウンロードするスクリプト

「The MagPi」は、Raspberry Piに関する情報を提供するマガジンです。チュートリアル、プロジェクトのアイデア、最新情報などを掲載し、コーディングやロボティクス、DIYエレクトロニクスに興味のある人々にとって役立つ内容が満載です。

The MagPi" のPDFは公式サイトで無料でダウンロードできます。最新号からバックナンバーまで、Raspberry Piに関する様々な記事やプロジェクトが掲載されています。公式サイトを訪れて、直接ダウンロードしてください。

Link: https://magpi.raspberrypi.com/issues

```python:download_magpi_all_issues.py
# 『The MagPi』雑誌の全号を一括ダウンロードするPythonスクリプト
import webbrowser
from time import sleep as wait

# 1から145までの範囲でループを実行
for i in range(1, 146):
    # 号のダウンロードURLを生成
    url = f"https://magpi.raspberrypi.com/issues/{i}/pdf/download"
    # ウェブブラウザでURLを開く
    webbrowser.open(url)
    # ページが完全に読み込まれるまで8秒間待機
    # インターネット接続が遅い場合は、ページが完全に読み込まれるように待機時間を増やすと良いでしょう。
    wait(8)
```
CPU使用率が高かったSQLクエリを抽出する方法(SQL Server)

# はじめに
WebサーバからDBへの接続の際にエラーが頻発しておりその調査をしていました。
その結果、特定の操作で実行されるSQLクエリでのCPU使用率が異常に高くなっていることがわかりました。


# 原因
該当のテーブルに適したインデックスが設定されていなかったことが原因でした。

# 対処
- 該当するテーブルに適したインデックスを追加する

上記の対処を実施することでWebサーバのエラーは無くなりました。
今回の調査で調べた内容を備忘録としてまとめておきます。

# CPU使用率が高かったSQLクエリの抽出
以下のクエリを実行することで、過去にCPU使用率が高かったSQLクエリを検索することができます。
```SQL:SQL
SELECT 
    st.text AS QueryText,
    qs.total_worker_time AS TotalCPUTime,
    qs.execution_count AS ExecutionCount,
    qs.total_worker_time / qs.execution_count AS AvgCPUTimePerExecution,
    qs.total_elapsed_time / qs.execution_count AS AvgElapsedTimePerExecution,
    qs.last_execution_time AS LastExecutionTime
FROM 
    sys.dm_exec_query_stats AS qs
CROSS APPLY 
    sys.dm_exec_sql_text(qs.sql_handle) AS st
ORDER BY 
    TotalCPUTime DESC;  -- CPU使用量の高い順に並べる
```

#### カラム説明
- `st.text` :
実行されたクエリのテキスト
どのクエリがリソースを消費しているかを特定できます。
- `qs.total_worker_time` :
クエリ全体で消費されたCPU時間
CPUを多く消費するクエリを特定できます。
- `qs.execution_count` :
クエリの実行回数
実行回数が多いクエリがシステムに負荷をかけているかどうかを判断するのに役立ちます。
- `qs.last_execution_time` :
クエリが最後に実行された日時

#### ビュー説明
- `sys.dm_exec_query_stats` :
SQL Serverの動的管理ビュー（DMV）の一つで、SQL Serverインスタンスで実行されたクエリ実行の統計情報を保持しています。
実行頻度が高く、CPUやI/Oのリソースを多く消費するクエリを特定するときに使用されます。


# インデックスの使用状況の確認
テーブルの検索のときに効率的なクエリの実行がされているかを確認します。
今回は以下のクエリを実行することで、データベース内の各テーブルのインデックスがどの程度使用されているかを取得しました。

```SQL:SQL
SELECT 
    OBJECT_NAME(ius.object_id) AS TableName,
    i.name AS IndexName,
    ius.user_seeks,
    ius.user_scans,
    ius.user_lookups,
    ius.user_updates
FROM 
    sys.dm_db_index_usage_stats AS ius
JOIN 
    sys.indexes AS i ON i.object_id = ius.object_id AND i.index_id = ius.index_id
```

#### カラム説明
- `OBJECT_NAME(ius.object_id)` :
テーブル名を表示
- `i.name` :
インデックスの名前を表示
- `ius.user_seeks` :
インデックスシークの回数
シークは、インデックスを使って特定のレコードを効率的に検索する操作です。
この数値が高いほどインデックスが効率的に使われていることを示します。
- `ius.user_scans` :
インデックススキャンの回数
スキャンは、インデックス全体を読み取る操作で、シークよりもリソースを多く消費します。
この数値が高い場合、インデックスが適切に設計されていない可能性があります。
- `ius.user_lookups` :
インデックスルックアップの回数
ルックアップは、インデックスから見つけたキーを使って実際のデータを取得する操作です。
この数値が多いと、パフォーマンスに悪影響を与える可能性があります。
- `ius.user_updates` :
インデックスの更新回数
インデックスの更新が頻繁に行われる場合、インデックスの維持にリソースが割かれている可能性があります。

#### ビュー説明
- `sys.dm_db_index_usage_stats` :
インデックスがどの程度使用されているかを追跡する動的管理ビュー（DMV）
インデックスがどのように使用されているかに関する統計情報を提供します。
- `sys.indexes` :
テーブルやビューに対するすべてのインデックスの詳細情報を保持するシステムビュー
各インデックスの構造、特性、および状態に関する情報を提供します。

# まとめ
まだまだ知らない機能や便利な機能があるということを知ることができました。
今後はもっとパフォーマンス面にも踏み込んだ設計と実装を行っていきたいです。
ロリポップのライトプラン利用時のFTPS接続の設定手順【VSCode】

# はじめに
ロリポップのライトプランを利用している方向けに、Visual Studio Code (VSCode) で安全にFTPS接続を設定し、サーバー上のファイルを直接編集する方法を解説します。

# 1. ロリポップでのFTPS接続情報の確認

まず最初に、ロリポップのFTPS接続情報を確認します。
以下の手順でロリポップのユーザー専用ページにアクセスし、必要な情報を取得しましょう。

## ロリポップのユーザー専用ページにログイン
[ロリポップのユーザー専用ページ](https://user.lolipop.jp/)にログインします。

## FTP接続情報の確認
ログイン後、左側のメニューから「ユーザー設定」→「アカウント情報」をクリックします。
ここで表示されるサーバー情報を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/b9fd2151-154c-39e8-4146-47f9d81c811d.png)

- **ホスト名（FTPサーバー名）**: `ftp.lolipop.jp`
- **ユーザー名（FTP,WebDAVアカウント）**: ロリポップで設定したFTPユーザー名
- **パスワード（FTP,WebDAVパスワード）**: FTPアカウントのパスワード

# 2. VSCodeに必要な拡張機能をインストール

次に、VSCodeにFTP接続を行うための拡張機能「SFTP」をインストールします。

## 拡張機能をインストール
サイドバーの「拡張機能」アイコン（四角いブロックのアイコン）をクリックし、検索バーに「SFTP」と入力します。
検索結果から、 **SFTP（作者: liximomo）** を選択し、インストールします。

# 3. VSCodeでのFTPS設定ファイルの作成

拡張機能をインストールしたら、実際に接続設定を行います。
`sftp.json`という設定ファイルを作成して、接続情報を記述します。

## コマンドパレットを開く
`Ctrl + Shift + P`（Windows/Linux）または `Cmd + Shift + P`（Mac）を押して、コマンドパレットを開きます。

## SFTP: Config Setup を選択
コマンドパレットで「SFTP: Config Setup」と入力し、選択します。
これにより、プロジェクトのルートディレクトリに `.vscode/sftp.json` ファイルが生成されます。

## sftp.jsonファイルに接続情報を入力
`sftp.json` ファイルを開き、以下の情報を記述します。
`remotePath`にはロリポップの公開ディレクトリである`/`を指定します。

https://lolipop.jp/manual/hp/ftp-set/

```sftp.json
{
    "name": "my-sftp-config", // 任意の名前
    "host": "ftp.lolipop.jp",
    "port": 21,
    "username": "FTPユーザーID",
    "password": "FTPパスワード",
    "protocol": "ftp",
    "secure": true,
    "remotePath": "/",  // ロリポップの公開ディレクトリ
    "uploadOnSave": true,  // ファイル保存時に自動アップロード
    "passive": true,  // パッシブモードで接続
    "debug": true  // デバッグモードを有効にする
    "encoding": "utf8" //文字化け対策
}
```

# 接続
SFTPを選択して、ファイルが同期されていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/25b026a1-f98c-641e-b0fd-67a6314e244a.png)
WSL】だれだよProgram Filesってディレクトリ作ったの！（スペースが入ったディレクトリ名をPATHに含める方法）

だれだよ、Program Filesってディレクトリ作ったの！
という時のメモです

Linuxの場合、PATHにスペースの入ったディレクトリ名があるとうまく行かない場合があります。
その場合の対処方です。

- WSLのシェルで```env|grep PATH```で現在のPATHを表示
- スペースを含むパスが無いかよく見る
- Windowsのコマンドプロンプトを開く(cmdを実行する）
- dir /xを使って、スペースを含むディレクトリの8.3形式の名前を調べる
　PROGRA~1が8.3形式の名前です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/d74f5269-4e2b-e16f-5fbf-4c17f6e8132a.png)

修正したPATHは、~/.bashrcに書く等ご自由に使ってください。
パイプライン演算子のない言語でパイプライン演算もどき(関数の連鎖)を実現する方法


# はじめに
カリー化を理解しようとサンプルを考えていたら、パイプライン演算子もどきの関数を思いついたので紹介します

# パイプライン演算子とは
最近お気に入りのjuliaという言語では関数を連鎖的に実行してくれるパイプライン演算子という構文があります

https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E6%BC%94%E7%AE%97%E5%AD%90

```julia:juliaの例
const HALFWIDEDIFF = 'ａ' - 'a'
function toWideChar(str::String)
  return replace(str, r"[a-zA-Z0-9]" => x -> Char(codepoint(x[1]) + HALFWIDEDIFF))
end
function toHalfChar(str::String)
  return replace(str, r"[Ａ-Ｚａ-ｚ０-９]" => x -> Char(codepoint(x[1]) - HALFWIDEDIFF))
end

s = " hello " |>
    strip |>      #両端の空白を削除
    uppercase |>  #大文字
    toWideChar    #全角

println(s) #ＨＥＬＬＯ
```

# javascriptでは
末尾の()は連鎖の終了を判定させるために必要です
出来ればつけなくて良いようにしたいのですが、良い方法は思いつきませんでした
```javascript
const HALFWIDEDIFF = "ａ".charCodeAt(0) - "a".charCodeAt(0);
function toWideChar(str) {
  return str.replace(/[a-zA-Z0-9]/g, x => String.fromCharCode(x.charCodeAt(0) + HALFWIDEDIFF));
}
function toHalfChar(str) {
  return str.replace(/[Ａ-Ｚａ-ｚ０-９]/g, x => String.fromCharCode(x.charCodeAt(0) - HALFWIDEDIFF));
}
const trim = x => x.trim()
const toUpperCase = x => x.toUpperCase()

function pipeFunc(value) {
  return function (func = null) { return func ? pipeFunc(func(value)) : value};
};

const s = pipeFunc(" hello ")
  (trim)           // (x => x.trim()) でもok
  (toUpperCase)    // (x => x.toUpperCase()) でもok
  (toWideChar)()
console.log(s) //ＨＥＬＬＯ 
```

### typescript
```typescript
function pipeFunc(value: any): Function  {
  return function (func?: Function): any { return func ? pipeFunc(func(value)) : value};
}
```

### 類似の方法
参考までに、reduceを使った類似の方法も思いついたので紹介します
```javascript
function pipeFuncs(initialValue, ...funcs) { 
  return funcs.reduce((value, currentFunc) => currentFunc(value), initialValue);
}
const str = pipeFuncs(" hello "
  , trim          // x => x.trim() でもok
  , toUpperCase   // x => x.toUpperCase() でもok
  , toWideChar
)
console.log(str) //ＨＥＬＬＯ 
```

### typescript
```typescript
function pipeFuncs(initialValue: any, ...funcs: Function[]): any {
  return funcs.reduce((value, currentFunc) => currentFunc(value), initialValue);
}
```
Explore Data-Driven Testing


https://trailhead.salesforce.com/content/learn/modules/testing-methodologies/explore-data-driven-testing?trail_id=explore-software-testing

全く意味が理解できない。答えはDらしい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/d2e9e7f7-dc3b-ec27-f8fd-1f83dfcfec96.png)
LaravelとReactで画像アップロードを作成する

# はじめに

こんにちは、エンジニアのkeitaMaxです。

Laravel10とInertia.jsでReactを使用して画像アップロードをし、保存する処理を作成したいと思います。

# Laravel側

保存する処理を作成します。

まず、web.phpでAPIの作成と、アップロード画面を表示するためのRouteを作ります。

```php:web.php
Route::post('/image_upload', [ImageController::class, 'index']);
Route::post('/api/upload', [ImageController::class, 'upload'])->withoutMiddleware(VerifyCsrfToken::class)->name('upload');
```

次に`ImageController`を作成していきます。

```php:ImageController
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Image;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ImageController extends Controller
{
    public function index(Request $request)
    {
        return Inertia::render('Image');
    }
    
    public function upload(Request $request)
    {
        $path = $request->file('image')->store('images', 'public');
        
        $url = config('app.image_url') . Storage::url($path);
        return response()->json(['url' => $url], 201);
    }
}

```

indexでReactの画面を表示していて、uploadで実際にアップロードした画像を保存してURLを作成して返しています。

# React側

以下のように作成しました。

```tsx:Image.tsx

import React, { useState } from 'react';

export const Image = React.memo(function Image() {

    const [image, setImage] = useState(null);

    const handleImageChange = (e: any) => {
        setImage(e.target.files[0]);
    };

    const handleSubmit = async (e: any) => {
        e.preventDefault();
        if (!image) return
        const formData = new FormData();
        formData.append('image', image);

        try {
            const response = await fetch('/api/upload', {
                method: 'POST',
                body: formData,
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                },
            });

            if (!response.ok) {
                throw new Error('ネットワークエラーが発生しました。');
            }

            const result = await response.json();
            console.log(result); // サーバーからのレスポンスを処理
            alert("画像がアップロードされました。");
        } catch (error) {
            console.error('エラー:', error);
            alert("画像のアップロードに失敗しました。");
        }
    };

    return (
        <div>
            <h1>画像アップロード</h1>
            <form onSubmit={handleSubmit}>
                <input type="file" onChange={handleImageChange} />
                <button type="submit">アップロード</button>
            </form>
        </div>
    );
})

export default Image

```

`npm run dev`をすると以下のような画面がでてくるので、画像をアップロードすると、URLが返ってきます。

![スクリーンショット 2024-09-08 20.30.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3715123/7f823784-87e5-85a8-f5cf-28e3d814ba5b.png)

# 画像を見る

返ってきたURLにアクセスしただけだと画像をみることはできません。

下のコマンドを実行するとみれるようになります。

```
php artisan storage:link
```

# おわりに

この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。

最後まで読んでいただきありがとうございました！

# 参考

https://qiita.com/keitaMax/items/80183b72f03cea1ccb23
AWSでFiveMのテストサーバを作ってみた

# FiveMのテストサーバについて

FiveMでscriptsの開発を実施することになり、常に使うわけではないため、従量課金であるAWSを利用することにした。（メインのアカウントもあるので）
今回は、Organizationで管理しながら、FiveM用の専用アカウントを作成し、その上で、EC2を構築した。
費用を下げるために、スポットインスタンスを利用してコスト削減も実施。


## 構成の概要図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3868301/258cbec2-5ce5-aa29-ffbf-caef196d240a.png)

## スペック
t3.large(CPU:2コア、メモリ:8GB)
ディスク：80GB
※上記スペックで作成したが、開発・テスト用ではもっとスペックが低くてもよさそう。
　特にディスクは40GBでも良さそうなぐらい使用していない。

## 構成のポイント
・Cfx.reでの登録上、固定グローバルIPを使用する必要があるため、EIPを利用する。
・開発サーバのため、スポットインスタンスを利用し、サーバのコスト削減（６０％OFFとか）
・EBSのスナップショットやAMIを作成して、インストール直後からの検証などやり直しをできる構成とした。（EIPのため、EC2側は紐付け変えるだけで使いまわし可能）

## 費用について
２週間で63時間程度の起動で約＄10（2024.9.1 約1,500円）
→単純に倍にして、１ヶ月で$20程度（約3,000円）

内訳は以下の通り
・EC2（t3.large Linux/UNIX Spot Instance）... 63.391 Hours → ＄2.69
・EBS (SSD (gp2) provisioned storage ... 45.138 GB-Mo　→　＄5.65
・EBS（snapshot data stored）... 4.546 GB-Mo → ＄0.23
・Elastic IP ... 355.514 Hours → $1.78

こう見るとEBSの比率が高いので、頻繁に作業をしないのであれば、検証の都度EBSのスナップショットを作成して、EC2インスタンス（スポットリクエスト）を削除し、使うたびにEC2を起動（作成）する方が安くなるかもしれない。

## EC2デプロイ時の注意（スポットインスタンス選択）
通常通り、EC2をデプロイするとオンデマンド料金になるので、EC2作成時にスポットインスタンスを選択する必要があり、また、中断動作を停止にしないとEC2が削除されてしまいます。
（休止は制約があるので停止が無難）

![スクリーンショット 2024-09-16 7.16.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3868301/33318df4-bf74-919e-115f-301bb9681056.png)


なお、EC2を削除する際は、スポットリクエストから削除する必要あり。
（EC2が再作成される）

## EC2デプロイ後の作業について
・SecurityGroupの設定
・MariaDBのインストール
・FiveMのインストール
このあたりのインストールについては、別途ブログでまとめられているので、以下を参照して作成。

https://gamesv.agepote.jp/game-server/xserver-vps-fivem

※上記サイトで、AWSの場合はEIPで通信する部分と、ローカルIPで通信する部分など読み替えが必要な箇所あり（FWの設定やMariaDBの設定など）

※EC2の停止防止漏れのために、EventBridgeで朝4時に停止するように設定。

## AWSでの構築メリットについて
・従量制課金＆スポットインスタンスのため、費用を比較的安く抑えられる。
・既存のAWSアカウントを持っていると簡単に始められる。
・バックアップ/リストアが容易にでき、EIPでグローバルIPを他サーバでも使えるため、ロールバックが簡単にできる。

## AWSでの構築デメリット（課題）について
・柔軟にできる分、AWSの知識がないと難易度が高い。
・レンタルサーバに比べて、それほど費用メリットはない。（ディスクをもっと削れば費用削減できる）
・EC2は稼働時間での課金だけど、EBSはどうしても恒常的に費用が発生する。

## 感想
今回、AWSでFiveMサーバを構築したが、もともとアカウントを持っていたのと、別ゲームでも同様のことをしていたので、簡単に実装することができた。
はじめてOrganizationsを使ったが、使ってみると管理かかなり楽であるのと、スイッチロールでメインのアカウントから簡単に操作できるので、最初の手間はあれど運用する上では便利だった。
今後は、scriptsの開発の記事なども作成していこうと思います。（記事を記載時点で３つほど開発済み）
[p5.js] p5.jsとTypeScriptで作る軽量ビルド: 1つのファイルで公開する方法

# はじめに
これは、p5.js、TypeScript、Vite、VSCodeを使ってクリエイティブコーディングした作品を、[Neort](https://neort.io/)などの作品共有サイトで公開するのに適したビルド方法についての説明です。

VSCodeとViteでコード補完機能を利用しながらコーディングするには、p5.jsをyarnまたはnpmでインストールして開発する必要があります。しかし、Viteでビルドすると、npmパッケージが含まれるため、生成されるJavaScriptファイルが大きくなり、Neortでの公開には適していません。

この記事では、トランスパイルされたコードをHTMLに埋め込むことで、ビルドサイズを抑え、Neortなどでの公開に適した方法を紹介します。

## 開発環境
- Visual Studio Code(or Cursor)
- p5.js
- TypeScript
- Vite

# npmパッケージをCDNに置換
ビルド時にp5.jsのコードをバンドルせず、CDN経由で読み込むことで、ビルドサイズを抑えます。npmパッケージのインポートをCDN経由の読み込みに置き換えるには、`vite-plugin-cdn-import`を使用します。

コード内で、次のようにp5.jsをimportしている場合に有効です。
```typescript
import p5 from "p5";
```

#### インストール
```bash
$ yarn add -D vite-plugin-cdn-import
```

#### 設定
`vite.config.js`に設定します。
```diff_javascript:vite.config.js
+ import cdn from "vite-plugin-cdn-import";

...

export default ({ mode }) => {
  return defineConfig({
    plugins: [
+     cdn({
+       modules: [
+         {
+           name: "p5",
+           var: "p5",
+           path: "https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js",
+         },
+       ],
+     }),
      
      ....
      
    ],
  });
};
```

# コードをHTMLに埋め込む
Viteでビルドすると、TypeScriptコードはJavaScriptにトランスパイルされ、別々のファイルとしてバンドルされます。その結果、ビルド後には複数のファイルが生成されます。`vite-plugin-singlefile`を使用すると、トランスパイルされたJavaScriptコードを直接HTMLに埋め込んで、ビルド結果を単一のファイルにまとめることができます。

#### インストール
```bash
$ yarn add -D vite-plugin-singlefile
```

### 設定
```diff_javascript:vite.config.js
import cdn from "vite-plugin-cdn-import";

...

export default ({ mode }) => {
  return defineConfig({
    plugins: [
+     viteSingleFile(),
      cdn({
        modules: [
          {
            name: "p5",
            var: "p5",
            path: "https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js",
          },
        ],
      }),
      
      ....
      
    ],
  });
};
```

# ビルド結果

```html:index.html
<!doctype html>
<html lang="en">
  <head>
    //CDNの読み込みに置換
    <script src="https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js" crossorigin="anonymous"></script>

    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Original Sketch</title>
    //インラインのコード
    <script type="module" crossorigin>(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;f
...
    new p5(h);</script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

```
Arduino UNO 3でコンデンサーマイク入力、Grove - Sound Sensorで遊ぶ





参考

![o_coq411.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/d553342b-788a-85fa-b807-b2420c4ffce5.jpeg)


↓クリック

https://wiki.seeedstudio.com/Grove-Sound_Sensor/

↑クリック

結果


![o_coq412.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/e5341d79-419a-0cdc-eb88-70b19131ecc7.jpeg)


![o_coq413.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/7ace7d8c-5013-fb79-c2e7-e89f49e51371.jpeg)


プログラム(サンプルそのまま)



```c++

// test code for Grove - Sound Sensor
// loovee @ 2016-8-30

const int pinAdc = A0;

void setup()
{
    Serial.begin(115200);
    //Serial.println("Grove - Sound Sensor Test...");
}

void loop()
{
    long sum = 0;
    for(int i=0; i<32; i++)
    {
        sum += analogRead(pinAdc);
    }

    sum >>= 5;

    Serial.println(sum);
    delay(10);
}



```
wslでmanim その25

# 概要
wslでmanimやってみた。
練習問題やってみた。

# 練習問題
ルービックキューブを描け。




# 成果物

![test_ManimCE_v0.18.1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/aca3d3ad-15cf-944a-1316-d0fe18e5f8a2.gif)


# サンプルコード

```

from manim import *

class RubiksCube(VGroup):
	def __init__(self, **kwargs):
		self.colors = ["#FFD500", "#C41E3A", "#009E60", "#FF5800", "#0051BA", "#FFFFFF"]
		vectors = [OUT, RIGHT, UP, LEFT, DOWN, IN]
		faces = [self.create_face(color, vector) for color, vector in zip(self.colors, vectors)]
		VGroup.__init__(self, *faces, **kwargs)
		self.set_shade_in_3d(True)
	def create_face(self, color, vector):
		squares = VGroup(*[self.create_square(color) for x in range(9)])
		squares.arrange_in_grid(3, 3, buff = 0)
		squares.set_width(2)
		squares.move_to(OUT, OUT)
		squares.apply_matrix(z_to_vector(vector))
		return squares
	def create_square(self, color):
		square = Square(stroke_width = 3, stroke_color = BLACK, fill_color = color, fill_opacity = 1, side_length = 1, )
		square.flip()
		return square
	def get_face(self, vect):
		self.sort(lambda p: np.dot(p, vect))
		return self[-(12 + 9):]

class test(ThreeDScene):
	def construct(self):
		cube = RubiksCube()
		cube.set_fill(opacity = 0.8)
		cube.set_stroke(width = 1)
		axes = ThreeDAxes()
		self.add(axes, cube)
		self.move_camera(phi = 70 * DEGREES, theta = -140 * DEGREES, )
		self.begin_ambient_camera_rotation(rate = 0.02)
		self.wait(2)
		self.play(Rotate(cube, TAU / 4, RIGHT, run_time = 3))
		self.wait(2)
		self.play(Rotate(cube, TAU / 4, UP, run_time = 3))
		self.wait(2)
		self.play(Rotate(cube, -TAU / 3, np.ones(3), run_time = 3))
		self.wait(2)




```

以上。
Pardotの項目

[Pardotの質問と回答をまとめに戻る](https://qiita.com/Keiji_otsubo/items/a3ccd1370697dd914723)


+ [Pardot フィールドマッピング](https://qiita.com/Keiji_otsubo/items/2daf9d6f52d1eb28fb6d)
+ [フォーム送信日時をプロスペクトカスタム項目に保持したい](https://trailhead.salesforce.com/ja/trailblazer-community/feed/0D54S00000Jg8DASAZ)
+ [フォームの複数選択項目の値をリセットする方法について](https://trailhead.salesforce.com/ja/trailblazer-community/feed/0D54V00007cmLZJ)

:::note
＞また、カタログ請求項目にたとえば「選択無し」という選択肢を入れるのはチェックボックス的に見栄えが悪いなあとも思っていますが、そうせざるを得ないでしょうかね。

当初のとおり、↑ とは思いましたが、項目の内容的にそれを作っておき、フォーム上では非表示にして置けばその動作になる、と思い直し、
併せてフロー上の通知対象から「選択無し」を除外することで、想定通りの動きを実現できました。
:::

+ [Account Engagement フォーム項目「電話番号」で、ハイフンがついていてもついていなくても、問題なく入力できる方法はないでしょうか？（「090-1234-5678」でも「09012345678」入力できるようにしたい）](https://trailhead.salesforce.com/trailblazer-community/feed/0D54V00007ifnmm)

:::note
アイデアに上がっています。ただ、このアイデアより前の投稿では項目の設定を数値から
テキストに変えれば対応できると回答されています。

Phone number with hyphens (-) and plus (+)　
https://ideas.salesforce.com/s/idea/a0B8W00000GdbspUAB/phone-number-with-hyphens-and-plus-

This will depend entirely on the data format set for the field. If it is set to 'number' then it will only accept numerical characters (0 - 9) and will error out with dashes. If, however, the field was set to 'text' data format, it would accept the dash, numbers, letters... anything, really.

By default, the phone field is set to Text type in Pardot, so you should just be able to edit the field settings within your form and change the data type from number to text.

https://trailhead.salesforce.com/trailblazer-community/feed/0D53A000033nNMxSAM
:::

+ [見込み客の最終オープン日と最終クリック日をエクスポートしようとしています。これらのフィールドはどこかに存在しますか?](https://trailhead.salesforce.com/trailblazer-community/feed/0D54V00007ifj2u)

:::note
B2B マーケティング分析を有効にしている場合は、見込み客アクティビティ データセットで見込み客アクティビティ データを表示できます。

参考関連文書:

https://help.salesforce.com/s/articleView?id=sf.pardot_b2bma_prospect_activity_dataset.htm&type=5

https://www.salesforceben.com/exploring-the-prospect-and-activity-dataset-in-b2b-marketing-analytics/
:::
wslでmanim その24

# 概要
wslでmanimやってみた。
練習問題やってみた。

# 練習問題
コッホ曲線を描け。


# 参考にしたページ

https://github.com/pdcxs/ManimProjects/blob/master/other/l_system.py


# 成果物

![LSystem_ManimCE_v0.18.1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/0e846f2b-e88b-9c23-469a-1615285d949c.gif)

# サンプルコード

```

from manim import *

class LSystem(Scene):
	def setup(self):
		self.rules = {'F': 'F+F--F+F'}
		self.angle = PI / 3
		self.start_rot = 0
		self.initial = 'F'
		self.actions = {}
		self.locations = []
		self.rotations = []
		self.graphs = []
		self.expression = ''
		self.animation = None
		self.weight = 1
		self.iteration = 3
		self.start_loc = (6.0 - 0.3) * LEFT + DOWN
		self.step_time = 0.1
		self.length = 0.5
		self.actions['F'] = self.draw_forward
		self.actions['+'] = self.rotate_forward
		self.actions['-'] = self.rotate_backward
		self.actions['['] = self.push
		self.actions[']'] = self.pop
		self.cur_loc = self.start_loc
		self.cur_rot = self.start_rot
		self.expression = self.initial
		self.animation = lambda x: self.add(x)
	def draw_forward(self):
		o = self.cur_loc
		l = self.length
		a = self.cur_rot
		e = o + l * np.cos(a) * RIGHT + l * np.sin(a) * UP
		self.cur_loc = e
		g = Line(o, e)
		g.stroke_width = self.weight
		self.animation(g)
	def rotate_forward(self):
		self.cur_rot += self.angle
	def rotate_backward(self):
		self.cur_rot -= self.angle
	def push(self):
		self.locations.append(self.cur_loc)
		self.rotations.append(self.cur_rot)
	def pop(self):
		self.cur_loc = self.locations.pop()
		self.cur_rot = self.rotations.pop()
	def generate(self):
		for i in range(self.iteration):
			print(f'generating iteration {i+1}')
			new_exp = ''
			for e in self.expression:
				new_exp += self.rules.get(e, e)
			self.expression = new_exp
			print(f'iteration {i+1} is finished')
	def draw(self):
		count = self.expression.count("F")
		print(f'Total {count} Fs')
		for e in self.expression:
			act = self.actions.get(e, None)
			if act is not None:
				act()
	def construct(self):
		self.setup()
		self.generate()
		self.draw()
		self.wait()





```

以上。
Google Apps Scriptを利用して、Discordへ定期的にメッセージを投稿する

# はじめに
Discordに定期的にメッセージを投稿したい時の方法を説明します。
Googleアカウントを持っていれば無料で誰でも作れます！
Webhookを使用します。

# やり方
## Webhookの準備
1. 定期的な投稿をしたいチャンネルのチャンネル設定を開きます
1. 「連携サービス」から「ウェブフックを作成」
![IMG_0409.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/f4dcff85-5791-3ddd-9153-d8ed5d2360be.jpeg)
1. 適当なアイコン、名前を設定しましょう
1. 「ウェブフックURLをコピー」を押し、URLを控えておきます
![IMG_0411.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/7150321d-be09-f5cc-0be2-36c3575b8a78.jpeg)

## Google Apps Script
1. Googleスプレッドシートを作成します
1. 「拡張機能」から「Apps Script」を押します
![IMG_1030.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/87b536cd-879a-dd23-669a-2b4e9ba0c006.jpeg)
1. コード欄に以下のコードをコピペします
```js
function myFunction() {
  const WEBHOOK_URL = ""; //コピーしたWebhookURLをペースト

  const payload = {
    content: "投稿するテキスト"
  };

  UrlFetchApp.fetch(WEBHOOK_URL, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
  });
}
```
:::note info
- 2行目に、先ほどコピーしたWebhookURLをペーストしてください
- 5行目の「投稿するテキスト」をお好きなメッセージに置き換えてください
:::

4. 保存して実行してみましょう
権限の確認が求められますが、全てOKを押しておけば大丈夫です。
![IMG_1032.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/f3fd27bc-468d-45ca-cc78-6f5ccf4e5891.jpeg)

5. Discordを開いてみると...!
**きてる！！**
![IMG_1033.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/f9de3b89-96b3-c767-57c5-9224d102067d.jpeg)


## トリガー設定
1. 「トリガー」を押します
![IMG_1035.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/a218abfe-a4ca-205b-08c7-4da88353d526.jpeg)

1. 「トリガーを追加」を押します
1. 下の画像のように設定します
![IMG_1036.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3772616/bc4c6daa-1415-cb69-8874-a7d557bcecc8.jpeg)

:::note info
「イベントのソースを選択」以下の設定を変更することで、メッセージの投稿間隔を自由に設定することができます。
:::

# まとめ
- Google Apps ScriptとWebhookを使うことで、無料で簡単にメッセージを投稿できる！
オプショナルチェイニング演算子の理解とその効果

※自分の学習メモとしても残しております。

オブジェクトに属するメソッドにアクセスするには、ドット演算を利用するのが基本。

オプショナルチェイニング演算子（?.）がない場合にエラーとなる理由は、null や undefined に対してメソッドを呼び出そうとすると、JavaScriptはそのオブジェクトが存在しないため、エラーを投げるからです。

## オプショナルチェイニングがない場合の動作
次のコードを例に考えてみます。
```
const str = null;
console.log(str.substring(3));
```
この場合、str は null であり、substring(3) メソッドは null には存在しないため、以下のようなエラーが発生します。

```
TypeError: Cannot read properties of null (reading 'substring')
```


### エラーが発生する理由

1. **null や undefined にはプロパティやメソッドがない**
 null と undefined は特別な値であり、これらにはメソッドやプロパティが存在しません。
2. **メソッド呼び出しをしようとする**
str.substring(3) は、str がオブジェクトであり、substring というメソッドが存在することを前提にしています。しかし、str が null の場合、substring メソッドは存在しないため、JavaScriptエンジンは「プロパティ 'substring' を 'null' から読み取ることができない」というエラー（TypeError）をスローします。

## オプショナルチェイニング演算子（?.）を使用する場合の動作
一方で、オプショナルチェイニング演算子（?.）を使用すると、JavaScriptは str が null または undefined かどうかをまず確認します。もしそうであれば、メソッド呼び出しをスキップして undefined を返すので、エラーを防ぎます。

以下のコードを例にしてみましょう。
```
const str = null;
console.log(str?.substring(3));  // undefined
```
このコードでは、str が null であるため、str?.substring(3) は単に undefined を返し、エラーは発生しません。

### substring メソッドについて
substring メソッドは、文字列の指定した開始位置から部分文字列を返します。

たとえば、以下のように書くと
```
const str = "Hello, World!";
console.log(str.substring(3));  // "lo, World!"
```
この場合、substring(3) はインデックス3から始まる部分文字列、つまり "lo, World!" を返します。**インデックスは0から始まるため、3は4番目の文字を指します。**

### substring と slice の違い
似たメソッドに slice があります。これらのメソッドはほとんど同じ動作をしますが、次の違いがあります。

- substring は負の値を 0 として扱います
- slice は負の値を文字列の末尾からの相対位置として扱います

この違いも頭に入れておくと、状況に応じて適切に使い分けることができます。

## まとめ
オプショナルチェイニング演算子を使うことで、null や undefined に対して安全にプロパティやメソッドアクセスを試みることができ、これによってプログラムの実行中に予期せぬエラーが発生するのを防ぎます。