OmniAICreator/Qiita-1.07M huggingface dataset:
個人的備忘録：ポップアップ実装には@headlessui/reactが良さそうだったので記事にまとめてみた

## はじめに

Next.js（TypeScript）とTailwind CSSを使ったフロントエンド開発において、ボタンを押した際に表示されるポップアップ（ダイアログ）を検討する機会がありました。

:::note info
**個人の備忘録程度の走り書きとなっておりますが、温かい目で見守っていただければ幸いです。**
:::

今回は、その中でも最も有名かつ相性の良いライブラリについて調査し、採用したので簡単にまとめておきます。

## 書こうと思ったきっかけ

ポップアップを実装する際、Next.jsとTailwind CSSとの親和性が高く、かつ軽量でメンテナンスが活発なライブラリを探していました。

調査を進める中で、@headlessui/reactが最も定番であり、Tailwind Labsが公式に提供していること、アクセシビリティへの配慮も十分であることを知り、これを選定することにしました。

また、公式サイト（`https://headlessui.com`）で情報が整理されており、導入も簡単だったため、備忘録としてまとめることにしました。

## ライブラリ比較表

| ライブラリ名        | 特徴                                             | 備考 |
|--------------------|------------------------------------------------|------|
| @headlessui/react   | Tailwind CSS公式チーム提供。スタイルレスで柔軟性抜群。アクセシビリティ考慮。 | Next.js+Tailwind CSSに最適。軽量かつ公式サポートあり。 |
| shadcn/ui           | Tailwind CSSベースで即使えるコンポーネント集。内部で@headlessui/reactを使用。 | 最新のトレンド。導入がやや簡単。 |
| Radix UI            | スタイルレスなアクセシブルコンポーネント。デザイン自由度高い。 | シンプル・軽量志向におすすめ。 |
| react-modal         | 古くからあるモーダル専用ライブラリ。アクセシビリティ対応。 | 最低限のモーダルだけほしい場合に有効。 |
| react-hot-toast     | 通知（トースト）用。ポップアップというより軽量通知向け。 | 軽いフィードバック表示に最適。 |

**参考文献**

https://headlessui.com/

https://qiita.com/twrcd1227/items/d4a67bb155503fde30f5

https://zenn.dev/microcms/articles/radixui-range-component

https://zenn.dev/643866/articles/054ad31487ff86

https://react-hot-toast.com/

## まとめ

Next.js（TypeScript）+ Tailwind CSS環境でポップアップ（ダイアログ）を実装する場合、@headlessui/reactが最も有名であり、安心して利用できる選択肢だと感じました。

**以下のポイントが採用理由となりました。**

- Next.jsとの相性が非常に良い
- 軽量でパフォーマンスにも優れている
- Tailwind CSSとの組み合わせが前提で設計されている
- アクセシビリティへの配慮が行き届いている
- メンテナンスが活発で安心感がある

今後、ポップアップ機能を実装する際は、まず@headlessui/reactをベースに検討するのが良いと感じました。

公式サイト（`https://headlessui.com`）も参考にしながら、プロジェクトに合わせてカスタマイズしていきたいと思います...！
【ネットワーク】CDN

# はじめに

こんにちは。アメリカに住みながら、独学でエンジニアを目指しているTairaです。

現在インターネットの知識の確認するインターネットクイズを定期的にやっているのですが、そこで出てきたCDNについて説明していきたいと思います。

---

## CDNとは？

CDN（Content Delivery Network）は、Webサイトのコンテンツ（画像、JS、CSSなど）を世界中のユーザーに高速・安全に配信するための分散型ネットワークです。世界各地に配置されたエッジサーバーを通じて、ユーザーの近くからデータを届ける仕組みです。

---

## なぜCDNの理解が重要なのか？

CDNは、モダンなWebアプリ開発においてインフラの一部として不可欠な存在です。以下の理由から、ソフトウェアエンジニアにとってCDNの理解はとても重要です。

- **表示速度とユーザー体験の向上**：ページ表示が早くなることでUXが改善され、直帰率の低下やコンバージョン率の向上が期待できます。
-  **スケーラビリティの確保**：トラフィックが集中してもCDNが分散して処理するため、アプリ全体の耐久性が高まります。
-  **グローバル対応**：海外ユーザーにも快適に利用してもらうためには、エッジサーバーを通じた配信が不可欠です。
-  **セキュリティと可用性の強化**：DDoS攻撃の緩和やWAF（Web Application Firewall）との連携によって、信頼性の高いサービス提供が可能になります。

---

## RailsやReactアプリでのCDN活用例

RailsやRails + Reactの開発では、以下のような場面でCDNが活用されます：

- 画像・動画など静的ファイルの高速配信（例：S3 + CloudFront）
- JavaScriptやCSSなどビルドされたアセットの配信
- トップページや商品一覧など、更新頻度が低いページの一部キャッシュ
- APIレスポンスの一部キャッシュ（読み取り専用の情報など）

---

## CDN導入時のポイント

以下の点を押さえておくと、CDN導入時に混乱せずに済みます。

### 1. アセットの準備とCDNへの配置

RailsやReactでビルドした静的ファイルは、CDNが読み込める場所（例：S3など）に配置する必要があります。

### 2. アプリケーションでCDNを指定

Railsでは `config.asset_host` にCDNのURLを指定することで、出力されるタグがCDN経由になります。

### 3. キャッシュの更新対策

ファイル名にハッシュを付けることで、CDNに古いファイルが残る問題を防ぎます（Railsは自動で対応）。

### 4. API連携時のCORS対応

ReactとRails APIを組み合わせる場合、オリジンが異なるとエラーになるため、CORSの設定が必要です。

---

## よくある構成図

```
[ユーザー]
     ↓
CloudFront（CDN） ← JS/CSS/画像 → S3
     ↓
Railsサーバー（動的処理） ← API → DB
```

---

## まとめ

CDNを活用することで、Webアプリケーションのパフォーマンス、スケーラビリティ、セキュリティを大幅に向上させることが可能です。特にRailsやReactのようなモダンな技術を使った開発では、初期の段階からCDNの設計を意識することで、以下のようなメリットを享受できます。

- 表示速度とユーザー体験の向上

- スケーラビリティの確保

- グローバル対応

- セキュリティと可用性の強化
TypeScriptでmapメソッドにオブジェクト型の値をコールバック関数に渡す方法

以下のように書く

```ts
type User = {
  id: number;
  name: string;
  age: number;
};

const users: User[] = [
  { id: 1, name: "Alice", age: 25 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Charlie", age: 35 }
];

const getUserName = (u: User): string => {
  return u.name
}

const getUserAge = (u: User): number => {
  return u.age
}
console.log(users.map(getUserName));
console.log(users.map(getUserAge));
```

初歩的だけど、引数()の後に:を付けて戻り値を宣言する
https://typescriptbook.jp/reference/functions/function-type-declaration
Mybatis resultMapの使い方

## resultMapの使い方


1. **カラム名とフィールド名が異なる場合**。
2. **ネストされたオブジェクトをマッピングする場合**。
3. **複雑な型変換やカスタムマッピングが必要な場合**。

### **基本例：カラム名とフィールド名が異なる場合**

#### **前提条件**
- データベースのテーブル名：`users`
- テーブルのカラム：
  - `id`（主キー）
  - `user_name`（ユーザ名）
  - `email`

```java:User.java（取得結果を格納するエンティティクラス）
@Data
public class User {
    private Long id;
    private String username; // カラム名は `user_name`
    private String email;
}
```

以下のように`resultMap`を定義して、カラム名とフィールド名のマッピングルールを記述します。

```xml:UserMapper.xml
<mapper namespace="com.example.UserMapper">

    <!-- ResultMapの定義 -->
    <resultMap id="UserResultMap" type="com.example.User">
        <id column="id" property="id" />
        <result column="user_name" property="username" />
        <result column="email" property="email" />
    </resultMap>

    <!-- クエリ -->
    <select id="findAllUsers" resultMap="UserResultMap">
        SELECT id, user_name, email FROM users
    </select>

</mapper>
```


```java:UserMapper.java
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface UserMapper {
    List<User> findAllUsers();
}
```


### **応用例：ネストされたオブジェクトをマッピングする場合**

#### **前提条件**
- `users`テーブルに加えて、`addresses`テーブルが存在。
  - `users`テーブル：
    - `id`
    - `user_name`
    - `email`
  - `addresses`テーブル：
    - `id`
    - `user_id`（`users.id`との外部キー）
    - `street`
    - `city`

```java:Address.java
@Data
public class Address {
    private Long id;
    private String street;
    private String city;
}
```

```java:User.java
@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private Address address; // ネストされたオブジェクト
}
```
以下のように`resultMap`内で`association`を使い、ネストされたオブジェクトをマッピングします。

```xml name=UserMapper.xml
<mapper namespace="com.example.UserMapper">

    <!-- AddressのResultMap -->
    <resultMap id="AddressResultMap" type="com.example.Address">
        <id column="id" property="id" />
        <result column="street" property="street" />
        <result column="city" property="city" />
    </resultMap>

    <!-- UserのResultMap -->
    <resultMap id="UserResultMap" type="com.example.User">
        <id column="id" property="id" />
        <result column="user_name" property="username" />
        <result column="email" property="email" />
        <association property="address" resultMap="AddressResultMap" />
    </resultMap>

    <!-- クエリ -->
    <select id="findAllUsersWithAddress" resultMap="UserResultMap">
        SELECT u.id AS id, u.user_name AS user_name, u.email AS email,
               a.id AS address_id, a.street AS street, a.city AS city
        FROM users u
        LEFT JOIN addresses a ON u.id = a.user_id
    </select>

</mapper>
```
google-drive-ocamlfuse - Gooole Driveをマウント

# 概要
[自分メモ]
Google Drive を Ubuntuで参照したい。
個人使用目的
※一般公開OAuthアプリは要審査

# 環境
- Windows 11 
- wsl: 2.5.7.0
- Ubuntu: 24.04.1 LTS

# クライアント導入

```shell
sudo add-apt-repository ppa:alessandro-strada/ppa
sudo apt update
sudo apt install -y google-drive-ocamlfuse
```

# Google Driveの設定

1. [Google Drive API設定ページ](https://console.cloud.google.com/marketplace/product/google/drive.googleapis.com)
2. APIを有効にする [MANAGE(管理)]
3. [APIとサービス] > [OAuth consent screen(同意画面)] > [Clients(クライアント)] > クライアント新規作成 > "デスクトップアプリ"を追加 
4. Client ID と Client secret を採取
5. [Audience(対象)] > [Test user] > 利用者(自分)のgmailアドレス追加


# 使用

```shell: 認証とマウント
GOOGLEDRIVE_CLIENT_ID=xxxxxxxxxx.apps.googleusercontent.com
GOOGLEDRIVE_SECRET=XXX-YYY-ZZZ
mkdir ~/gdrive
google-drive-ocamlfuse -id $GOOGLEDRIVE_CLIENT_ID -secret $GOOGLEDRIVE_SECRET ~/gdrive
```
未認証の場合ブラウザが開く。認証後、ブラウザクローズ。 GUIがない場合は `-headless`オプションをつける


```shell:確認
mount | grep google-drive-ocamlfuse

# こんな感じで表示されたらOK
# google-drive-ocamlfuse on /home/shokkaa/gdrive type fuse.google-drive-ocamlfuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)
```



# 参考

https://github.com/astrada/google-drive-ocamlfuse
Mybatis ORDER BY句に${ }が使用できない件

### 結論
`ORDER BY`句に変数を渡す際に、`#{}（プレースホルダー形式）`ではなく、`${}（文字列展開形式）`を使用する必要があります。

---

### **理由**
1. **`#{}（プレースホルダー形式）`**
   - SQL文に値をバインドする際に使用される

2. **`${}（文字列展開形式）`**
   - 値をそのままSQL文の一部として挿入する
   - **SQL構文の一部（カラム名、テーブル名、`ORDER BY`句など）を動的に構築する場合に使用される**。

---
### **正しい使い方**

```xml name=UserMapper.xml
<mapper namespace="com.example.UserMapper">
    <select id="findUsers" resultType="com.example.User">
        SELECT id, user_name, email
        FROM users
        ORDER BY ${orderBy} <!-- 動的なカラム名 -->
    </select>
</mapper>
```

---

### **注意点**
`${}`を使用する際には、SQLインジェクションを防ぐために、入力された値を以下のように検証することが推奨される

**許可リストを使用**:
   動的に渡される値が想定されたカラム名やソート順であることを確認します。

```java
private final String CREATED_AT = "created_at";

public UserDto list(Pageable pageable, String sortColumn, Integer sortOrder) {
  // 指定可能なソート列名が複数ある場合も考慮
  String[] allows = {CREATED_AT}; 
  // 引数.ソート列名とソート順の相関チェック
  this.mismatchSortOrder(allows, sortColumn, sortOrder);

  // 以降処理省略
}

public void mismatchSortOrder(String[] allows, String sortColumn, Integer sortOrder) {

    if (!StringUtils.isEmpty(sortColumn) && !Arrays.asList(allows).contains(sortColumn)) {
      // 不正な物理名が指定された場合
      throw new BusinessException(HttpStatus.BAD_REQUEST, MessageKey.VALIDATION_ERROR.getValue(),
          "invalidSortColumnName:" + sortColumn);
    }
    if ((!StringUtils.isEmpty(sortColumn) && Objects.isNull(sortOrder))
        || StringUtils.isEmpty(sortColumn) && Objects.nonNull(sortOrder)) {
      // どちらか片方のみに値が設定されている場合
      throw new BusinessException(HttpStatus.BAD_REQUEST, MessageKey.VALIDATION_ERROR.getValue(),
          "One of the two parameters is null sortColumn:" + sortColumn + " sortOrder:" + sortOrder);
    }
}
```


---
SQL Developerで新規のSQLワークシートを開きたい

# はじめに

SQL Developerで新規のSQLワークシートを開く手順をいくつか紹介します。

(自分用のメモです。たまにド忘れするので)

# ①「接続」から開く

接続タブから対象の接続を選択し、右クリック。「SQLワークシートを開く(U)」を選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105859/13c93d2e-255d-93fe-07e3-ba3461259ba0.png)

# ②「ツール」から開く

画面右上の「ツール(T)」から「SQLワークシート(W)」を選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105859/567cf9c0-fe9a-22b9-0bd9-dcbea9f5b643.png)

「接続の選択」が表示された場合は、任意の接続を選択し「OK」を押下する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105859/1bbb118f-7415-5a24-efad-ca1fad7a0515.png)

# ③ショートカットから開く

**Alt + F10**を押下する。「接続の選択」が表示された場合は、任意の接続を選択し「OK」を押下する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105859/1bbb118f-7415-5a24-efad-ca1fad7a0515.png)


# 環境情報

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105859/5aab7e43-0230-d49b-c165-6f47491e9a8f.png)
Cloudflare,Hono環境のRepositoryで環境変数を扱う

# Honoではprocess.envが使えない
Honoでの開発中、SupabaseのAPIキーを.dev.varsに記載して、以下のようにリポジトリで使おうと思ったところ、
```typescript:xxxRepository.ts
import { createClient } from "@supabase/supabase-js";


const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_ANON_KEY;

const supabase = createClient(supabaseUrl,supabaseKey);

```

```terminal:terminal
[ERROR]service core:Uncaught Error: supabaseUrl is required.
```

supabaseUrl,keyをログに出力してみると
```terminal:
"supabaseUrl" undefined
"supabaseKey" undefined
```

はい、取り出せてません。

公式ドキュメントによると、


>コードの中で c.env.* から環境変数にアクセスします。 
> **Cloudflare Workers では、環境変数には c からアクセスします。 process.env ではありません。**
<a>https://hono-ja.pages.dev/docs/getting-started/cloudflare-workers</a>

ということでした。

ただ... **context扱うのはcontroller** 

ということで、DIによるバケツリレーで解決

```typescript:xxxController.ts
import { Hono } from "hono";

// 環境変数の型定義
type Bindings = {
  SUPABASE_URL: string;
  SUPABASE_ANON_KEY: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.get("/", async (c) => {
  try {
    const repository = new xxxRepository(c.env.SUPABASE_URL, c.env.SUPABASE_ANON_KEY);
    const service = new xxxService(repository);

    const xxx = await service.getAll();
    return c.json(xxx, 200);
  } catch (error: unknown) {
    const { message, statusCode } = handleError(error);
    return c.json({ message }, statusCode);
  }
});
```

```typescript:xxxService.ts
export class xxxService {
  private repository: xxxRepository;

  constructor(repository: xxxRepository) {
    this.repository = repository;
  }

  async getAll(): Promise<xxx[]> {
    return await this.repository.findAll();
  }
  
}
```

```typescript:xxxRepository.ts
export class xxxRepository {
  private supabase;
  constructor(supabaseUrl: string, supabaseKey: string) {
    this.supabase = createClient(supabaseUrl, supabaseKey);
  }

  async findAll(): Promise<xxx[]> {
  
    const { data, error } = await this.supabase.from....

    return data as xxx[];
  }

}
```

値を渡してインスタンスを作ることで、あくまで依存性の方向は保ったまま、環境変数を扱いました。

**ただ、このバケツリレーが正解なのかどうかも分からない笑**
また公式ドキュメントと睨めっこしたり、いろんなコードを見ながら最適解を見つけます笑
【備忘録】Dockerビルドコンテキスト・.dockerignoreについて

# Dockerコマンドの実行順序

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348923/67f8f2c5-02f1-4734-b46d-e7d5fa0fa1b0.png)

- `Docker CLI`は実行コマンドを解析して、`REST API`を叩く
- `Docker daemon`は`Docker`のそれらを実現する本体

# ビルドコンテキスト・ビルドコンテキストパスとは
- **ビルドコンテキスト**はイメージビルドに必要なファイル群
- **ビルドコンテキストパス**は**ビルドコンテキスト**の一番親階層に当たるパス

# docker image build と ビルドコンテキストパス
- 実際に`Dockerイメージ`をビルドするのは`Docker daemon`
- `Docker CLI`から`Docker daemon`に対してイメージビルド時に必要なファイル群を全て送信する必要がある
- その送信対象を決めるのが**ビルドコンテキストパス**
- ビルドコンテキストの中に`Dockerfile`が無い場合は`-f`オプションで`Dockerfile`のパスを指定する

# .dockerignore とは
- ビルドコンテキストに含めたくないファイルを記載するファイル
    - ファイルサイズの大きいファイルが`Docker daemon`に送られて、ビルドに時間がかかるのを防ぐために指定する
    - 機密情報をイメージに持たせないために指定する
【2025年】Latexでヒラギノフォントを指定する話

# MacならLaTeX文章でヒラギノフォントを使いたい！
## 美しいフォントを...
フォントで文章のイメージは思った以上に変わるので，折角ならヒラギノフォントを使いたいと思いました。
## 方法
LuaLaTeXを用いると，次のように書くだけでできます。
```LaTeX
% LuaLaTeX文書
\documentclass{ltjsarticle}
\usepackage{luatexja-fontspec}  # これがポイント
\setmainjfont{HiraMinProN-W3}   # ヒラギノ明朝の場合
# \setmainjfont{HiraKakuProN-W3}   # ヒラギノ角ゴの場合

\begin{document}

こんにちは、LaTeX！
これでヒラギノフォントを使った文章を作成できます。
ああ、ヒラギノフォントは美しいですね。

\end{document}
```
簡単ですね🎶

## setmainjfontの名前はどこに...
どこに書いてあるのか実のところわかっていません。
明朝体の名前はPostScript名と同じでした（Font Bookなどで確認できます）。
ゴシック体の名前は，PostScript名を指定しても動きませんでした。
明朝体がHiraMinProN-W3なら角ゴシック体はHiraKakuProN-W3だろう，という思いつきで見つけました（え？）。

## 参考にさせていただいたサイト
色々なページでLuaLaTeXでのフォントの指定方法が説明されていました。
例えば，ZR-TeXnobabblerさんのブログも参考になりました。
『イマドキのmacOSで平和に“pLaTeXでヒラギノ”する話（※ただし画期的）』
https://zrbabbler.hatenablog.com/entry/2023/08/08/225414
ChatGPTで実用可能なアラームアプリを一撃作成

ChatGPTの進化は凄まじく生成スピードも当初とは比べ物にならないくらい速くなりました。今後もどんどん進化していくんでしょうね。

そこでなんとなくですが、タイトルに書いた通り出来るだけプログラミング用語を使わずに、プロンプト１回だけでアラームアプリが作れるかを試してみました。

使用したChatGPTは4oです。
そのプロンプトはこちらです。必要な機能をダーッと書き出しただけですので、ツッコミ不要です。
```
pythonで指定時間にアラームを表示するコードを書いて
起動時に数字のボタンを配置して、時間を4桁で入力して、OKボタンで時間を登録します。
時間まで待機中の表示を出して下さい。この表示には現在時刻と指定時刻とあと何分何秒で指定時間になるかを表示して「アラームをキャンセル」ボタンを配置して押したら終了してください。
指定した時間に「時間になりました！」と音声と文字で表示します。その表示に「消去」と「スヌーズ」とかいたボタンを配置して、消去を押したら終了。スヌーズを押したら1分後にまたお知らせするようにコードを書いてください。
また表示を消さないと30秒後に自動的にスヌーズボタンを押すようにしてください。
各表示は必ず最前面に表示してください。
「アラームをキャンセル」や「消去」などを押した際に使用していたタイマーを必ず終了してから全て終了してください。
```
特に推敲もしていません。

上記のプロンプトをChatGPTに投げると、数十秒でコードを生成してくれました。
速い・・・

メモ帳にChatGPTが生成してくれたコードをコピペして、拡張子をpyにしてPythonで実行します。

果たして・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/8c451589-50a6-41fd-b3cb-5bdab0bc9216.png)

おお、綺麗な入力画面が出てきました。
４桁の数字で時間を指定してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/d57fb1ce-ffd5-491d-b068-e4965392a96b.png)
待機時間表示もばっちりですね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3880604/409b40bf-6388-4ea7-b566-de3de09d9319.png)
そして指定時間になったらちゃんと表示してくれて・・・

きちんと自動でスヌーズもしてくれます。

なーんのトラブルもなくアラームアプリが作れてしまいました。
今のところメモリリークやバックグラウンド動作が残ることも無いようです。

唯一音声で「時間になりました！」と流してはくれなかったので、そこはプロンプトを少し修正すればできるようになると思います。

と言うことで、実験は大成功でした。

環境によって結果は異なるかもしれませんが、エラーの内容をChatGPTに伝えれば修正してくれますし、ほんとに便利になったものです。

実は初期のChatGPTでもアラームアプリを作ったことがあるのですが、その時はさんざんエラーになって修正を繰り返した覚えがあります。

今回も一撃でできるわけないですよね～と気軽に始めた企画だったのですが

AIの進化は素晴らしい！

あえて生成されｔコードは載せませんが、Pythonの環境が整っていれば使えるコードを生成してくれますので、みなさんも上記のプロンプトを使って試してみてください。

ちなみにo3では一撃でとはいきませんが、数回エラーの修正を指示したところ使えるようになりました^^
自己紹介 — Ruby on Railsエンジニア・ClinicSyncプロジェクト

![Ruby on Railsプログラマーがコンピュータでプログラミングしているイラスト](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwd3aoc3s6c140ikbg7k.png)

はじめまして！

レオナルド・クアドロス・フラゴーゾ（Leonardo Quadros Fragozo）と申します。  
レオ（Leo）と呼んでください。  
私は**Ruby on Rails**を中心に開発を行っている**Webアプリケーション開発者**です。

現在、**ClinicSync**というクリニック管理プラットフォームを開発中です。  
このプロジェクトは、私が受講している [Ruby On Rails MBA](https://mbaonrails.com.br/) コースの学習記録も兼ねています。

📚 興味のある分野：

- Ruby on Rails
- Hotwire（Turbo + Stimulus）
- TailwindCSS
- 自動テスト（RSpec）

技術力を日々向上させ、構造化されスケーラブルなソリューションを提供できるよう努めています。

🚀 GitHubでClinicSyncプロジェクトをご覧ください: [GitHub - ClinicSync](https://github.com/FragozoLeonardo/Clinic-Sync)

---

📖 日本語学習について

日本語の勉強も続けています。  
現在は以下の教材を使用しています：

![](https://storage.googleapis.com/zenn-user-upload/e3d1148f52c9-20250427.png)

- **[YomuYomu.app](https://yomuyomu.app/)**（読解と聴解練習用）

日本語とプログラミングの両方を成長させていきたいと考えています。

最後まで読んでいただきありがとうございます！
OpenRouterを触ってみる

## はじめに
最近、LineBotとAIを組み合わせた機能を作りたいなと考えています。
OpenRouterがAIのモデルを色々仕え、さらに無料枠があるということなので触っていきたいと思います！

## OpenRouterとは
複数のAIモデルを1つのAPI経由で利用できる。chatgptやgemini、claude等のモデルが異なる場合、apiのInterfaceが異なるため、それぞれのモデルの形式に沿ったAPIを叩く必要がある。これが面倒なので、OpenRouterは1つのInterfaceで複数のモデルをいい感じに利用できるよってやつ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/bee815ae-0cd9-496b-b7ee-ef3835848e39.png)


https://openrouter.ai/

## 触ってみる
まずは、OpernRouterのサインアップからしていきます！！
下記URLからアカウント作成を実施してください。

https://openrouter.ai/

・APIキーを発行する
では、APIのkeyを発行します。今回はtestというAPIkeyを発行します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/c484cdce-0bc5-4326-a183-7879a655411d.png)


## 実際にAPIを叩く
```shell:環境変数を設定する
~  $ # ① APIキーを環境変数にセット
     export OPENROUTER_API_KEY="...." //先ほど生成したAPIKeyを設定します
```

``` shell:openrouterの実行コマンド
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemini-2.0-flash-exp:free",
    "messages": [
      {"role": "system", "content": "あなたは親切なアシスタントです。"},
      {"role": "user", "content": "大阪の今日の天気を教えてください。"}
    ]
  }'
```

```shell:実行結果
{
	"id": "gen-1745715209-fgvOs1Rq3EqUPYULIiS7",
	"provider": "Google AI Studio",
	"model": "google/gemini-2.0-flash-exp:free",
	"object": "chat.completion",
	"created": 1745715209,
	"choices": [
		{
			"logprobs": null,
			"finish_reason": "stop",
			"native_finish_reason": "STOP",
			"index": 0,
			"message": {
				"role": "assistant",
				"content": "大阪の今日の天気は晴れ時々曇りです。最高気温は31℃、最低気温は23℃です。\n\n他に何か知りたいことはありますか？\n",
				"refusal": null,
				"reasoning": null
			}
		}
	],
	"usage": {
		"prompt_tokens": 16,
		"completion_tokens": 35,
		"total_tokens": 51
	}
}
```
→返答が返ってきましたね！！
Androidアプリ開発でAPI通信を実現する

### 使用するライブラリ
#### Retrofit
http通信を簡単に実現するためのライブラリ。JavaやKotlinのインターフェースだけでAPI呼び出しをすることができる。
- httpリクエストを簡単に実装できる
- サーバーからのレスポンスを自動的にデータクラスに変換できる
- 通信エラー処理やリトライ制御もできる

#### Gson
JSONデータとオブジェクトの相互変換をするためのライブラリ。Googleが提供している
- JSON→データクラスへの変換（デシリアライズ）
- データクラス→JSON文字列への変換（シリアライズ）
- ネスト構造のある複雑なJSONにも対応可能

Rertofitは通信を担当し、Gsonはデータの変換を担当する。

### 実装イメージ
[TheMealDB]("https://www.themealdb.com/api.php)というAPIを使用する場合を想定して実装

gradleファイルに以下を追加して、プロジェクトにライブラリを追加する
``` build.gradle
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
```

続けて、API通信に必要なデータクラス、通信処理をじｓ
``` Category.kt
// カテゴリが持つデータを定義
data class Category(
    val idCategory: String,
    val strCategory: String,
    val strCategoryThumb: String,
    val strCategoryDescription: String
)

// API通信のレスポンスを定義
data class CategoriesResponse(val categories: List<Category>)
```

``` ApiService.kt
// API通信のためのインターフェースを定義
interface ApiService {
    //`categories.php`エンドポイントに対してGETリクエストを送信する
    // suspend関数で定義をしているのでコルーチン内で呼び出す
    @GET("categories.php")
    suspend fun getCategories(): CategoriesResponse
}

// Retrofitインスタンスを作成する
private val retrofit = Retrofit.Builder()
    // APIのペースURLを設定。この後ろにエンドポイントが付く
    .baseUrl("https://www.themealdb.com/api/json/v1/1/")
    // Gsonを使用してJSONデータクラスに変換する
    .addConverterFactory(GsonConverterFactory.create())
    .build()

// ApiServiseインターフェースを実体化して、APIリクエストを使えるようにする
val recipeService = retrofit.create(ApiService::class.java)
```

``` CategoryViewModel.kt
class CategoryViewModel: ViewModel() {

    private val _categoriesState = mutableStateOf(RecipeState())
    val categoriesState: State<RecipeState> = _categoriesState

    // ViewModelが作成されたときにデータ取得処理を行う
    init {
        fetchCategories()
    }

     private fun fetchCategories() {
         // 非同期処理（コルーチン）内でAPI通信を行う
         viewModelScope.launch {
             try {
                 // 正常にデータ取得した場合、RecipeStateを更新してデータ取得が完了したことをUIに知らせる
                 val response = recipeService.getCategories()
                 _categoriesState.value = _categoriesState.value.copy(
                     loading = false,
                     list = response.categories,
                     error = null
                 )
             } catch (e: Exception) {
                 _categoriesState.value = _categoriesState.value.copy(
                     loading = false,
                     error = "データ取得エラー：${e.message}"
                 )
             }
         }
     }

    data class RecipeState(
        val loading: Boolean = true,
        val list: List<Category> = emptyList(),
        val error: String? = null
    )
}
```

API通信自体はsuspendで定義されているため、コルーチンを使用して呼び出す必要があるためviewModel.launchを使用して非同期処理を行っています。

### suspendを使う意味は？
その関数が一時停止することで、非同期で安全に実行できるようにするため。
API通信などの時間がかかる処理は同期的に処理が走ると、アプリが止まってしまいユーザーにとって使いにくいアプリになってしまうため、スムーズに処理を行うために非同期で処理を行う。
【CSS】背景色がスライドするボタン

# はじめに
CSSについて勉強中です。勉強で理解したことを備忘録として残しておきます。
今回はCSSを使って背景色がスライドするボタンを作成しました。

# ボタンの表示
![gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3977234/39ce57b6-b685-4401-aa93-1f1922cef3d9.gif)


# ファイル内容
作成したHTMLファイルとCSSファイルです。設定したスタイルの内容は後で説明します。


```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div id="container">
      <button class="btn slide-bg"><span></span>Button</button>
    </div>
  </body>
</html>
```

```css
#container {
  text-align: center;
}
.btn {
  background-color: white;
  color: black;
  border: 1px solid black;
  padding: 10px 40px;
  margin: 50px 0;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s;
}
.btn.slide-bg {
  position: relative;
  overflow: hidden;
  z-index: 1;
}
.btn.slide-bg span {
  display: inline-block;
  width: 100%;
  height: 100%;
  background-color: black;
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(-100%);
  transition: transform 0.3s;
  z-index: -1;
}
.btn.slide-bg:hover {
  color: white;
}
.btn.slide-bg:hover span {
  transform: none;
}
```

# HTMLファイル内容
```html
<button class="btn slide-bg">
  <span></span>Button
</button>
```
- `span`要素は、アニメーション用の背景
- `button`の中に入れて、背景がスライドするようにしている

# CSSファイル内容
## ボタンのベーススタイル
```css
.btn {
  background-color: white;
  color: black;
  border: 1px solid black;
  padding: 10px 40px;
  margin: 50px 0;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s;
}
```
- 白背景 + 黒文字のシンプルなボタンにしている
- `transition: all 0.3s` で滑らかな変化をつける

## 背景スライド用の設定
```css
.btn.slide-bg {
  position: relative;
  overflow: hidden;
  z-index: 1;
}
```
- `position: relative;`：子要素（span）を絶対配置するための基準
- `overflow: hidden;`：ボタン外に背景がはみ出さないようにする
- `z-index: 1;`：文字が背景より前に出るようにする

## スライドさせる背景
```css
.btn.slide-bg span {
  display: inline-block;
  width: 100%;
  height: 100%;
  background-color: black;
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(-100%);
  transition: transform 0.3s;
  z-index: -1;
}
```
- `position: absolute;`：ボタンの中に広がるように配置
- `transform: translateX(-100%)`：初期状態でボタンの左に完全はみ出るように移動している
- `z-index: -1;`：背景になるように、文字よりも後ろに表示

## ホバー時の変化
```css
.btn.slide-bg:hover {
  color: white;
}
.btn.slide-bg:hover span {
  transform: none;
}
```
- 文字色が白に変化
- `span`がボタン内にスライドして、背景が黒に切り替わる

# おわりに
CSSの細かい設定は調べたり値を変化させたりしてみると理解が深まると思います。
なんとなくの理解だったJSONを改めて学習してみる

## JSONとは？
正式名称は`JavaScript Object Notation`といい、名前の通り元々はJavaScriptから生まれたが、現在では様々なプログラミング言語で使われているデータフォーマットになっている。

### 主な特徴
- テキストベースなのでデータが小さく、軽量で通信が早い
- シンプルな記法なため直感的に理解できる
- パース（読み取り）や生成が簡単
- Python, Javaなど、ほぼ全ての言語に対応している
- データを階層構造で表現することができる

### JSONの構造
- キーとバリューのペアで成り立っている
- キーはダブルクォーテーションで囲む
- バリューは文字列、数値、真偽値、配列など
```JSON
{
    "name": "Taro",
    "age": 24,
    "job": "engineer",
    "hobby": ["guitar", "soccer", "running"]
}
```

### JSONが使用される場面
主にサーバーとアプリ間でデータをやり取りするAPI通信の際に使用される。アプリからサーバーにリクエストを送信する場合や、サーバーからレスポンスを受け取ったときに、JSON形式でデータ変換される。
返却されたJSONは各言語ごとのオブジェクトに変換（デシリアライズ）してアプリ内で処理したり、逆にオブジェクトからJSONに変換（シリアライズ）して送信するケースもある。
【基本情報技術者試験 対策⑤】情報セキュリティマネジメント

 **💻基本情報技術者試験 テクノロジ系 技術要素・情報セキュリティ🔓**

前回の続きです！
情報セキュリティマネジメントについて、学習内容をまとめています。

 ## 「情報セキュリティマネジメント」とは？
> 情報セキュリティの確保に組織的、体系的に取り組むこと

 #### ■ISMS（Information Security Management System：情報セキュリティマネジメント）
 >組織の情報資産について、 **機密性、完全性、可用性** の３つをバランスよく維持・改善するための仕組み

 - **機密性** 認められた人だけが情報にアクセスできること
 - **完全性** データが改ざんされていないこと
 - **可用性** 必要な時にサービスを利用できること

 ## 脅威への対策
> 脅威に対する具体的なセキュリティ対策は大きく「人的セキュリティ対策」「技術的セキュリティ対策」「物理的セキュリティ対策」の３つに分類できる

#### ⑴人的セキュリティ対策：人によって引き起こされる脅威への対策

- **アクセス権**　ユーザーごとに与えられる、ファイルへの読み書き権限

#### ⑵技術的セキュリティ対策：技術的な手段によって引き起こされる脅威への対策

##### ■攻撃者からサーバを守るための **「3つの壁」** 
![セキュリティ3つの壁.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068752/929cb350-4de1-4bf1-b155-626579852fd0.jpeg)

- **ファイアウォール**　
インターネットを通じた不正アクセスから社内ネットワークを守るための仕組み  

- **IDSとIPS**
・IDS（Intrusion Detection System：侵入検知システム）
→サーバに対して外部から不正アクセスがあった際に、システム管理者に **通知** を行うシステム
・IPS（Intrusion Prevention System：侵入防止システム）
→サーバに対して外部から不正アクセスがあった際に、システム管理者に **通知** を行うと同時に、**そのアクセスを遮断する** システム  

- **WAF** (Web Application Firewall)
→Webアプリケーションの脆弱性を狙った攻撃からシステムを守るための仕組み

#### ⑶物理的セキュリティ対策：災害や破壊、妨害行為への対策

- **バイオメトリクス認証** 
→身体的特徴や行動的特徴による認証（指紋や虹彩、網膜、静脈パターン、筆跡や歩き方など）
※本人拒否率（システムが間違って本人を拒否する確率）が低いほど便利なシステム
　他人受入率（間違って他人を受け入れる確率）が低いほど安全なシステム
 
- **2要素認証**　記憶、所有物、生体情報の3つの要素のうち2つを使って本人確認をする認証方式

##### ■その他のネットワークに関する対策
- **DMZ** （DeMilitarized Zone：非武装地帯）
→外部ネットワークと社内ネットワーク両方から隔離されたネットワーク領域

- **SSL/TLS** （Secure Sockets Layer / Transport Layer Security）
→パソコンとサーバ間のやり取りを暗号化する通信プロトコル

- **HTTPS** （HyperText Transfer Protocol Secure）
→SSL/TLSを用いて、HTTPによる通信を暗号化するためのプロトコル
※暗号化の範囲は **WebブラウザからWebサーバの間**
- **WPA3**（Wi-Fi Protected Access 3）
→無線LAN上の通信を暗号化する規格
※暗号化の範囲は **無線LAN（PCとアクセスポイントの間）**

---

他にもマルウェア検出方法や、携帯端末に関する対策など
セキュリティ対策はたくさんあるので、今後追記していきます📝

### 参考文献
[いちばんやさしい 基本情報技術者 絶対合格の教科書＋出る順問題集（SBクリエイティブ公式サイト）](https://www.sbcr.jp/product/4815624262/)

[だれでもわかる＆受かる！　情報セキュリティマネジメントやさしいテキスト+頻出問題集[科目A・科目B]（SBクリエイティブ公式サイト）](https://www.sbcr.jp/product/4815616151/)
Actionで同じAPI名が既に存在している


+ [We can't find that the Experience Management topic has the Get Experience Details action added to it.](https://trailhead.salesforce.com/trailblazer-community/feed/0D5KX00000SV6Qu)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/5235d8eb-d78a-4583-9714-fddaae546c84.png)

:::note
You say it was deleted, but I think it's just the relationship between the topic and the action that was deleted. 


Try selecting the same action from the agent asset (library).
:::


# エージェントアクションの編集

+ [Editing an Agent Action](https://trailhead.salesforce.com/trailblazer-community/feed/0D5KX00000SVfA6)

:::note 
When you land on agent action detail page, there should see pencil icon available for fields which are editable. Once you click that, it should show edit screen.

エージェントアクションの詳細ページにアクセスすると、編集可能なフィールドに鉛筆アイコンが表示されます。それをクリックすると、編集画面が表示されます。
:::
Supabase-UI-Library OAuth認証 (Google、GitHub)＋匿名認証

Supabase-UI-Library を使って、Google認証、GitHub認証、匿名認証(AI製)を実装しました。

Supabase-UI-Library を使えば OAuthの設定(認証用のキーを取得する、Supabaseの設定)の手間だけで実装できます。

OAuth認証用のキーは各自で取得してください。
Supabaseのサーバーの設定は各自で設定をしてください。
匿名認証はOAuth認証を参考にGitHub Copilotで実装しています。

![uploading...0](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44761/c263fdf7-a457-4bc9-ad05-8033700382f2.png)



# この記事のリポジトリ

masakinihirota/Supabase-UI-Library

https://github.com/masakinihirota/Supabase-UI-Library

# README.md

素のNext.jsにSupabase UI Library の Social Authを追加して、ファイルを少し編集をしました。

# 採用技術
* Next.js 15 App router
* Supabase
* Supabase UI Library の Social Auth
を使って
Google Auth
GitHub Auth
匿名 Auth (GitHub Copilot製)
を実装しています。

Supabase UI Library
https://supabase.com/ui

* Drizzle ORM
* Biome
* lint-staged

supabase/config.toml の[analytics]などをfalseにしています。
理由はローカルでの動作を軽くしたり、trueにするとWindowsのローカル環境ではうまく動かないためです。

`.github/supabase/prompts/*`
これはSupabaseの公式の指示書です

supabase/examples/prompts at master · supabase/supabase

https://github.com/supabase/supabase/tree/master/examples/prompts
読書感想文「実践！プロジェクト管理入門」　ch8

# 書籍名「実践！プロジェクト管理入門」　ch8
著者：梅田 弘之
翔泳社

章ごとに分割して投稿します

https://qiita.com/RYA234/items/495f534798aa347ce44c

https://qiita.com/RYA234/items/026e2fe18865dbbd8926

# 読もうと思った動機
最近、CMMについて触れる機会があったため。


# 印象に残った部分

## p126 なぜ失敗は繰り返されるのか
システム開発の要因は、プログラム作成だけではなく、ネットワークやミドルウェア、セキュリティバックアップ、ハードウェア、開発言語といった異なるベンダ製品を組み合わせて
システムを構築するための幅広い知識と経験が必要とされるからだ。
もうひとつの要因は、開発期間やシステムの使いやすさ、デザイン性などのユーザ要求が従来とは比較にならないくらい複雑になっている。


## p130-131　PMBOKだけでは意味がない
PMBOKはあくまで一般のプロジェクトを管理するのに必要な知識レベルを体系化したガイドに過ぎず、それをシステム開発プロセス向けに解釈して注目するのはあくまでも自分たちなのだ。
大切なのは、PMBOKを参考にしたうえで、自分たちなりのプロジェクト管理手法を作成し、それを実践することである。

## p131 CMMも知っておこう
最近では、PMBOKと並んでCMM（Capacibility Maturity Model）というプロジェクトを改善する手法。CMMは企業単位の開発プロセス改善を目指すものである。
CMMを導入する目的は、「開発委託選定のための評価基準」、「自社のプロセス改善」の２つである

習熟度レベルは以下の通り。

成熟度レベル|概要
-----------|---
1.初期段階|基本的なプロジェクト管理もできていない。作業のやり方が場当たり的で、雑然としている。
2.再現可能段階|基本的なプロジェクト管理が実現可能、　スケジュール、コスト機能の面では初歩的な管理プロセスを確立している。同じような仕事を反復して成功することができるレベル
3.定義段階|組織的なプロジェクト管理が行なわれている。プロジェクト管理における管理プロセスと開発プロセスが文書で定義され、全プロジェクトｔでそれを遵守している。
4.管理段階|プロセスやプロダクトが定量的に管理されている。開発プロダクトの品質データを数値化し、データに基づいたプロセスの管理を遂行している。
5.最適化段階|プロセス改善に全員が積極的に参加している。改善活動が日常化していて、継続的に改善が図られている。

## p136　プロジェクト管理を強化するアプローチ
STEP1　プロジェクト管理項目を体系的に整理、理解する。（ＰＭＢＯＫなどを理解）
STEP2　自社の状況に合わせた管理手法として具体化する。（ＣＭＭのレベル２，レベル３に相当）
STEP3　手法をオーソライズし、メンバーに実施を義務付ける
STEP4　管理の実行を監視、管理し遂行を徹底する。
STEP5　結果を評価し、管理手法の不備を改善して次につなげる（同レベル５）



## p136
 STEP2で重要なのは「ドキュメントベース」ということだ。すなわち、手法自体を明文化することもさることながら、PMBOKプロセスの「出力」に相当する
 成果物を具体的なテンプレートとして使用できる。


　具体的な取り組む方は各社各様だと思うが、最初から完全を目指さないことが肝心である。PMBOKに書かれているといって、それを全て取り入れたのでは、
プロジェクトメンバーがついてきてくれないだろう。

## p143　できるところから実践すること
トップやラインマネージャの理解が無いような場合には、せっかくの知識や工夫が絵に描いた餅になりかねない。実は組織がプロジェクト管理力をつける腕難しい点はここなのである。しかし、そんなときでも諦めるのではなく、できる範囲からやればいいのである。
自プロジェクトだけでも実践すれば、それでも十分効果はあるし、効果があれば、やがて部門全体へと波及していくものとして考えることにしよう。



# 実践できること or 感想

## 感想

・ＣＭＭは会社の力量を図る指標の一つになると思いました。

・どこでも働けて、成果をだせるITエンジニア（プログラマであれ、インフラであれ）になるには、ある程度、ＰＭＢＯＫとＣＭＭを実践できる必要が有ると感じた。
ITエンジニアは一つの会社の在籍期間が短くなる傾向にあるので、転職するたびに会社、会社の上司を期待するのは運頼みで危険だと思います。自分自身でできれば運頼みにしなくて済みます。

・１０年後も十分使える分野だと思うので、習得すると無駄にならないと思いました。

## 実践できること

・p136　プロジェクト管理を強化するアプローチを実践する
　個人開発で試せるところを試す。
・CMMの入門書を買って読む
「はじめての開発のためのＣＭＭＩとプロセス改善」
gpt-image-1でディープフェイクの人物画像生成を試す

https://gpt-image.dev/

この記事では、OpenAIが提供する **gpt-image-1** モデルを用いて、深層学習を活用したいわゆる「ディープフェイク風」の人物画像を生成する手順を解説します。Web UIとして公開されている [gpt-image.dev](https://gpt-image.dev/) の紹介と、APIを直接叩くサンプルコードも合わせてご覧ください。

---

## はじめに

近年、深層学習を用いた画像生成技術は急速に発展し、リアルなフェイク画像（いわゆる「ディープフェイク」）の作成も一般的になってきました。  
OpenAIの最新モデル **gpt-image-1** は、人物の顔写真を入力（参考画像）として与えることで、その人物に似た新しい画像を生成できるのが特徴です。  
本記事では、  
- gpt-image-1 がどんなモデルか  
- Web UI「gpt-image.dev」の使い方  
- API経由で自前の環境から実行する方法  
- 実際の出力例  
といった流れで試してみます。

---

## 1. gpt-image-1 とは？

- **モデル種別**：OpenAI Image API の新世代モデル  
- **特徴**：  
  - 複数の参照画像を同時に与え、同一人物の別アングル・別シチュエーションの画像を生成  
  - 「人物ヘッドショット」用途に最適化  
  - 高品質なリアリスティック出力  

> ⚠️ ディープフェイク生成はプライバシー・肖像権に関わる重大な問題を含むため、公正な利用と法令遵守を厳守してください。

---

## 2. gpt-image.dev の利用

公式のWeb UI が https://gpt-image.dev/ で公開されています。APIキーを入力し、画像サイズや品質を選択、参照画像をアップロードして「Generate」ボタンを押すだけで手軽に試せます。

1. **APIキー設定**  
   OpenAIのダッシュボードから取得した API Key を貼り付け  
2. **サイズ・品質**  
   - サイズ（256×256, 512×512 など）  
   - 品質（クオリティレベルのスライダー）  
3. **参照ヘッドショットのアップロード**  
   同一人物の写真を 3～5 枚用意  
4. **プロンプト記述**  
   生成したいシチュエーションを日本語または英語で記述  
5. **生成ボタン押下**  

---

## 3. API を使った自前実装例

直接 cURL や SDK から Images API の `edits` エンドポイントを叩く例です。

```bash
#!/bin/bash
API_KEY="あなたの_OpenAI_API_Key"
# 参照画像 (同一人物の顔写真) を複数指定
IMAGE_FILES=( "ref1.jpg" "ref2.jpg" "ref3.jpg" )

curl https://api.openai.com/v1/images/edits \
  -H "Authorization: Bearer ${API_KEY}" \
  -F "model=gpt-image-1" \
  $(for img in "${IMAGE_FILES[@]}"; do echo -n "-F image[]=@${img} "; done) \
  -F "prompt=夜のネオン街を背景に、同じ人物が未来的なヘッドフォンを装着しているポートレートを生成して。" \
  -o output.json

# レスポンスから生成画像のURLを抽出して保存
jq -r '.data[0].url' output.json | xargs curl -o generated.png
```

- **model** に `gpt-image-1` を指定  
- `image[]` で参照画像を複数アップロード  
- `prompt` に生成内容を自然言語で記述  

---

## 4. 実際の出力例

![GpYi9ambQAAy7KR.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863976/c18bb056-dbfe-4b98-96f7-f556f34d0858.jpeg)
![GpYi9aZaAAAGZkW.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863976/c764c7dc-872f-4b56-8846-7b28bc8e0506.jpeg)
![GpYi9W-aAAA7PIn.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863976/5a3698da-dd14-40de-9fff-0c029aa426f3.jpeg)
![GpYi9W7bEAAPeHl.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863976/8c568a49-1fa1-42c3-8827-947ee3aa7546.jpeg)


> 上記はあくまでサンプルです。生成結果は入力画像やプロンプト文によって大きく変動します。

---

## 5. 応用と注意点

- **応用例**  
  - 映画やゲーム向けのプロトタイプキャラクター作成  
  - オンラインプロフィール写真の多バリエーション生成  
- **注意点**  
  - 著名人や第三者の顔写真を無断で用いたディープフェイクは法令違反・ポリシー違反の恐れ  
  - 商用利用の際はライセンス要件を必ず確認  
  - AIバイアスや倫理的問題への配慮  

---

## まとめ

- OpenAI **gpt-image-1** は、複数の参照画像をもとに高品質な「ディープフェイク風」人物画像を生成できる最先端モデルです。  
- Web UI の [gpt-image.dev](https://gpt-image.dev/) なら手軽に試用可能。API経由での実装も簡単です。  
- 実験的な利用からプロダクションへの応用まで、幅広いシーンで活用できる一方、倫理・法令の遵守が不可欠です。

ぜひ [gpt-image.dev](https://gpt-image.dev/) をチェックして、自分だけのオリジナルヘッドショットを生成してみてください！
Ubunt24.04でpip install時に"error: externally-managed-environment"エラーが出た時の対処法

# 概要

Ubuntu24.04でPythonのパッケージをpipでインストールしようとすると、以下のようなエラーメッセージが表示されてインストールが失敗することがあります。

```console
$ pip install numpy
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
   
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
   
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
   
    See /usr/share/doc/python3.12/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
```

このエラーの原因と解決策について紹介します。

## エラーの原因

　上記のエラーは、一部のOS（Ubuntu24.04やDebian12）において、システムPython（システム上にインストールされている、いわば大元のPython）に直接pipでパッケージをインストールすることを制限する機能により発生しています。
　後述の解決策1でも記載しているように、システムPythonに直接パッケージをインストールすることは、様々な問題を引き起こす原因となるため、基本的に非推奨となります。本エラーはこのようなシステムPython上へのパッケージインストールを防ぐため、Debian系OSにおいて実装されているようです。

## 解決策

以下の方法を用いることで、上記のエラーが発生する環境であってもpipでPythonのパッケージがインストールできるようになります。

- 解決策1: **仮想環境を使う（原則この方法を推奨）**
- 解決策2: **aptでPythonパッケージをインストールする**
- 解決策3: **`--break-system-packages`オプションを付ける**

### 解決策1: 仮想環境を使う（原則この方法を推奨）

　エラーメッセージにも`create a virtual environment`と書かれているように、本来Ubuntu上でPythonのパッケージを使用する際は仮想環境を構築することが推奨されています。これにより環境の再現を容易化したり、無秩序にパッケージがインストールされて不具合の原因となることを防ぐことができます。
　仮想環境の作成方法とパッケージのインストール方法については、以下の記事を参照ください（迷ったらデフォルトで使用できるvenvを使うと良いと思います）。

https://qiita.com/c60evaporator/items/b6a7394231d1e768ce64

　上記の記事はやや古いので、uv等の新しいツールについては触れていません（uvについては私も良く分かっていないので、適宜調べていただければと思います）。
　また、Python以外の環境も仮想化する手段として、Dockerを使用することも有効です。

### 解決策2: aptでPythonパッケージをインストールする

　ここからがこの記事の本題ですが、使用したいプラットフォームによっては、どうしてもシステムPythonにパッケージをインストールせざるを得ない状況が発生します。

　例えば私のケースでは、ROS（Robot Operating System）のノード内でPythonのパッケージを使用する必要がありました。ROSは基本的にシステムPythonを参照して動作するため、ノードからNumpy等のパッケージを利用するためには、システムPython上にインストールする必要が生じます。

　このようなケースにおいて、NumpyやPandas等の有名なパッケージは、aptを用いることでエラーを発生させることなくインストールすることができます。

・Numpyのインストール例

```bash
sudo apt update
sudo apt install python3-numpy
```

### 解決策3: `--break-system-packages`オプションを付ける

こちらは非推奨ですが、`pip install`時に`--break-system-packages`オプションを付けることで、上記エラーを発生させることなく強制的にパッケージをインストールすることができます。

・Numpyのインストール例

```bash
pip install numpy
```

この方法は、バージョンを指定したい場合等に便利です。

・バージョンを指定したNumpyのインストール例

```bash
pip install numpy==1.24.3 --break-system-packages
```
Unity DevOps Build AutomationがUnity Build Profilesを使ってビルドできるようになった

[Unity DevOps Build Automation](https://docs.unity.com/ugs/en-us/manual/devops/manual/build-automation/overview)（以下、Build Automation）が[Unity Build Profiles](https://docs.unity3d.com/6000.0/Documentation/Manual/BuildSettings.html)（以下、Build Profiles）を使ってビルドできるようになりました。

公式ドキュメントはこちら。

https://docs.unity.com/ugs/en-us/manual/devops/manual/build-automation/build-automation-editor-package

Build Automationのダッシュボードを開き、ConfigurationsのBasic infoセクションの中、「Build Profile path」に利用したいBuild Profileのパスを指定します。次に示すスクリーンショットの注意書きにもあるように、AssetsフォルダからのBuild Profileのパスを書いて下さい。（Assetsも含めて）

![スクリーンショット 2025-04-26 23.28.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/aba27e3f-6ea5-4d24-a286-71b48dd9e17c.png)

---

投稿者はいつからできるようになったかを、把握できていません（公式の資料を探しています）。

どうも[公式フォーラムのやりとり](https://discussions.unity.com/t/build-automation-feature-requests/933389/22)を見る限り、少なくとも2024年11月時点では、2025のQ1目標でリリースだった雰囲気を感じます。それから、別の[公式フォーラムのやりとり](https://discussions.unity.com/t/new-build-profile-from-unity-6-with-build-automation/1565849)を見ると、どうも2024年あたりからAPIだったら指定できたみたいです。

---

Build Profilesを使うことで、開発版・検証版・製品版などのビルド設定を簡単に切り替えることができます。

Build Profilesは非常に便利なのですが、今までBuild Automationが対応していませんでした。製品版のビルドをBuild Automationでしている投稿者は、Build Profilesの導入を躊躇していましたが、これを機に導入しようと思います。

* Unity Profilesは、Unity 6.0以降が対応しています。
* Unity 6.1では、Build Automationの設定から、[Build Profilesを作る機能](https://docs.unity.com/ugs/en-us/manual/devops/manual/build-automation/build-automation-editor-package)があるようです
クエリパラメータの利用例

# クエリパラメータとは
- URLの末尾の「?」のあとに指定するキーと値の組わせのことです。
- **WebページやAPIの動作をカスタマイズする**ために使われます。
- 「&」を指定することで、複数のパラメータを指定できます。

# クエリパラメータの利用例
検索
　http:\//localhost:8000/search?name=キーワード
言語
　http:\//localhost:8000/search?lang=ja
ソート、ページネーション
　http:\//localhost:8000/search?sort=desc

# FastAPIのクエリパラメータ
```python:main.py
from fastapi import FastAPI


app = FastAPI()


@app.get('/items/')
async def find_by_name(name: str):
    return {'name': name}

```
Python dot-env 覚書

# <font color="Red">dot-envの目的・機能</font>

「os.getenv()の機能（範囲）を拡張するためにdot-envを使う」

- プロジェクトに配置した「.envファイル」をロード
- ロードした.envの内容を、**os.getenv()** から取得可能

# <font color="Red">実際の使い方</font>
1. インストール
1. .envファイルの用意
1. 使い方
1. 動作

# <font color="HotPink">インストール</font>
```terminal
pip install python-dotenv
```

# <font color="HotPink">.envファイルの用意</font>
メモ帳などで、プロジェクト内に.envファイルを作成し「KEY=値」を書き込む。
以下のイメージ
```.env
KEY="value"
USER_NAME="username"
PASSWORD="password"
```
ちなみに、ターミナル上で　```$ echo "KEY=value" >> .env```　とすると .envファイルに追記できる。
※誤って　「>>」　でなく　「>」　とすると、記述済み内容をすべて消して最後に送信した　"KEY=value" だけが記録されるので注意（>>：append、>：output（上書き）というイメージ）。

# <font color="HotPink">使い方</font>
- import は、```from dotenv import load_dotenv```
- ```load_dotenv(file_path)``` で.envの内容を環境変数として認識
- ```os.getenv()``` メソッドで、.envの内容にアクセス可能

何らかのトークン（TOKEN="asfdhjk...")を.envに記載し、プログラムから取得したい場合。

```python
from pathlib import Path
import os
from dotenv import load_dotenv

env_file = Path(__file__).resolve().parent / ".env"

load_dotenv(path)
token =  os.getenv("TOKEN")
```

# <font color="HotPink">動作</font>

私は最初に認識を誤っていたので、記載しておく。
**<font color="Red">■誤認識■</font>** ：load_dotenv()の戻り値からインスタンスを取得して、load_dotenvインスタンスから.env内容を取得する、という操作をするのかと予想していたが、これは誤り。

**<font color="Red">■正解■</font>**：load_dotenv()により、環境変数に.envの内容が登録される　→　os.getenv()からの呼び出しが可能になる

なお、OS（システム）の環境変数を変更するわけではなく、プロジェクト内で一時的に登録してくれるだけらしい。
グローバルな環境は汚さないので、不安がる必要はない模様（コードを書けば書くほど、システムの環境変数が大量になる、といったことはない）
Java解説 20250427

### アクセス修飾子
- クラス、コンストラクタ、変数、メソッドに対し、他のクラスへのアクセス公開範囲を制限するもの。
- コードの先頭に記述されている。

|アクセス修飾子|概要|
|-----|----|
|public|すべてのクラスからアクセスできる|
|protected|現在のクラスとサブクラスからアクセスできる|
|なし|現在のクラスと同じパッケージのクラスからアクセスできる|
|private|現在のクラスからだけアクセスできる|
### 継承
- サブクラスを定義するには、「extends」を使用する。
- javaは単一継承の為、「extends」の後に指定できるクラスは1つのみ。
### オーバーライド
スーパークラスで定義されたメソッドをサブクラスで再定義することである。
### スーパークラス
継承元となるクラス。

<br>

[オラクル認定資格教科書 Javaプログラマ Bronze SE(試験番号1Z0-818)](https://www.amazon.co.jp/%E3%82%AA%E3%83%A9%E3%82%AF%E3%83%AB%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E6%95%99%E7%A7%91%E6%9B%B8-Java%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E-Bronze-SE-%E8%A9%A6%E9%A8%93%E7%95%AA%E5%8F%B71Z0-818/dp/479816206X/ref=pd_lpo_d_sccl_1/357-5313211-9846263?pd_rd_w=VQCzo&content-id=amzn1.sym.855d8f70-df76-4181-80b0-56e48ae3bb9b&pf_rd_p=855d8f70-df76-4181-80b0-56e48ae3bb9b&pf_rd_r=JRR4W9RKV32K4AXF1WRZ&pd_rd_wg=l7VoD&pd_rd_r=1d465f85-0562-4b51-b523-77f8486c1508&pd_rd_i=479816206X&psc=1 "オラクル認定資格教科書 Javaプログラマ Bronze SE(試験番号1Z0-818)")
236.Lowest Common Ancestor of a Binary Tree

# 問題内容
入力される２つの数字に共通する祖先のうち最も近い層にある祖先を探すプログラムを書く

```python:sample
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root or root == p or root == q:
            return root
        
        l = self.lowestCommonAncestor(root.left, p, q)
        r = self.lowestCommonAncestor(root.right, p, q)

        if l and r:
            return root
        return l or r
```

# ポイント
* 再帰的に呼び出すことで目的のノードが出力されるようにする
* 各点の呼び出しで最初にrootがNone、p、qのいずれに当てはまるかをチェックする
* 左右のサブツリーに目的ノードがあるか調べるために再帰呼び出し
* lとrの両方から値が帰ってくれば、その親ノードが目的ノード
* 片方から値が返され、もう片方がNoneなら返された値が目的ノードである
【Flutter】アプリアイコンを変更する

# はじめに

ホームに表示されるアプリアイコンを「flutter_launcher_icons」を使用して変更します。

https://pub.dev/packages/flutter_launcher_icons
# パッケージ追加

以下のコマンドでパッケージを追加します。

```dart
flutter pub add flutter_launcher_icons
```

# アプリアイコンの画像追加

アプリアイコン用の画像をassets配下に追加します。

※ 場所は任意

※ スプラッシュ画面で表示させたときの画像を使用しています

```dart
/qiita_memo/assets/images/app_icon/app_icon.png
```

# アイコンの設定を追加

pubspec.yamlにアイコンの設定を追加します。

```dart
# アプリアイコン
flutter_launcher_icons:       
  android: true
  ios: true
  remove_alpha_ios: true # アイコンの白色箇所を削除するのに必要
  image_path: assets/images/app_icon/app_icon.png # アプリアイコンに使用する画像
```

# アプリアイコンの生成

以下のコマンドでアプリアイコンを生成します。

```dart
flutter pub run flutter_launcher_icons:main
```

# アプリアイコン確認

flutter runなどでアプリを起動して、ホーム画面のアプリアイコンを確認すると反映されていました。

![スクリーンショット 2025-04-27 4.16.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3868196/554c83a4-083f-4287-8e90-15c324952982.png)


# 終わりに

アプリアイコンをデフォルトから変更しました。
パッケージを使うと簡単に変更することができました。
【Flutter】スプラッシュ画面を表示させる

# はじめに

パッケージ「flutter_native_splash」を使用してスプラッシュ画面を作成します。

https://pub.dev/packages/flutter_native_splash

# スプラッシュ画面追加

## flutter_native_splashを追加

以下のコマンドでflutter_native_splashをプロジェクトに追加します。

```dart
flutter pub add flutter_native_splash
```

## スプラッシュ画面用の画像配置

以下の場所にスプラッシュ画面の真ん中に表示させたい画像を置きます。

※ 一旦1000×1000pxのスプラッシュ用の画像ファイルを作成
※ファイル場所は任意

```dart
/assets/images/splash/splash.png
```

## スプラッシュ画面の設定

プロジェクト直下に `flutter_native_splash.yaml` を作成します。(pubspec.yamlと同様の階層)
その中に以下のようにスプラッシュ画面の設定を書きます。

※ 詳細な設定項目はpub.devのパッケージ説明参照
※ android_12の設定がないとandroid12以降で反映されない

```dart
# スプラッシュ画面設定
flutter_native_splash:
    color: "#ffffff" # スプラッシュ画面の背景色
    image: assets/images/splash/splash.png # スプラッシュ画面に表示する画像
    fullscreen: true # スプラッシュ画面をフルスクリーンにするかどうか
    android_12:
        image: assets/images/splash/splash.png # Android 12以降のスプラッシュ画面に表示する画像
        color: "#ffffff" # Android 12以降のスプラッシュ画面の背景色
```

## スプラッシュ画面生成コマンド実行

以下のコマンドを実行してスプラッシュ画面を作成します。

```dart
flutter pub run flutter_native_splash:create
```

# スプラッシュ表示中の初期化処理

スプラッシュ表示中に初期化処理をしたい場合は以下のコードを使用します。

```dart
void main() {
  // 以下のコードでスプラッシュ画面の削除のタイミングを制御できる
  // 初期化処理などを行ったあとにremove()を呼ぶ
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
  FlutterNativeSplash.remove();
  runApp(const MyApp());
}
```

## アプリ起動

`flutter run` などでアプリ起動時に以下のエラーが発生しました。

```dart
Your project is configured with Android NDK 26.3.11579264, but the following plugin(s) depend on a different Android NDK version:
- flutter_native_splash requires Android NDK 27.0.12077973
Fix this issue by using the highest Android NDK version (they are backward compatible).
Add the following to ~/qiita_memo/android/app/build.gradle.kts:

    android {
        ndkVersion = "27.0.12077973"
        ...
    }
```
NDKのバージョンを指定する必要がありそうでした。
NDKを確認してみたところ、 `27.0.12077973` がなく`29.0.13113456` が入っていたため、 `29.0.13113456` をndkVersionに指定しました。

再度アプリを起動させると、指定した画像が表示されたスプラッシュ画面が表示されました。

![スクリーンショット 2025-04-26 17.03.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3868196/37822269-8958-43bb-9b69-dc66f5ff05e9.png)


# まとめ

今回はFlutterのスプラッシュ画面の実装を行いました。
パッケージを使用することで簡単に実装することができました。
Geminiでオカリナを使う魔法少女を描いてみた。

# 概要

Geminiでオカリナを使う魔法少女を描いてみました。

# 実装

プロンプトと実行結果は以下の通りでした。

```
光のオカリナを使う魔法少女を描いてください。
```

![光のオカリナを使う魔法少女_20250427.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3192024/17ab957d-61ba-4e6d-905c-eceb94feb50f.png)


```
闇のオカリナを使う魔法少女を描いてください。
```

![闇のオカリナを使う魔法少女_20250427.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3192024/0a80fffa-8653-4dd9-abb8-69fdde48c8af.png)


```
聖なるオカリナを使う魔法少女を描いてください。
```

![聖なるオカリナを使う魔法少女_20250427.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3192024/9d883227-85ab-40d3-84bc-cb47ff5502a5.png)


# まとめ

光の子が可愛いかも。
何かの役に立てばと。
GCP_API_制限

# 【Android×Google Calendar API】APIキーにアプリ制限をかける方法とヘッダー設定まとめ

## はじめに
前回の記事では、APIキーだけで日本の祝日を取得する方法を紹介しました。  
今回は、さらに安全に運用するため、**GCP側でAPIキーにアプリ制限をかける方法**と、
**Android側で正しくヘッダーを設定する方法**についてまとめます。

## やりたいこと
特定のAndroidアプリからのみ Google Calendar API を呼び出せるように制限をかける

## ハマりポイント
1. 通信時にパッケージ名とフィンガープリントをヘッダーに載せる方法が分かりづらい
2. APIキー、パッケージ名、フィンガープリントをソースコードにハードコーディングしない設計に悩む
3. ヘッダーでのフィンガープリントは「:」不要

## やったこと
1. GCPコンソール上でアプリケーションの制限を実施  
    - APIキーの設定画面で、アプリケーション制限を以下に設定  
      「アプリケーションの制限」 → Androidアプリ  
      「パッケージ名」（例: com.example.XX）  
      「フィンガープリント」（例: XX:XX:XX:XX:...）

2. Androidアプリ側でのAPIリクエスト実装  
    - 通信時に、X-Android-Package と X-Android-Cert ヘッダーを付与するように実装  
    - APIキーやフィンガープリントはハードコーディングせず、環境変数で管理

## 実装

### APIリクエスト
```kotlin
    private val transport = NetHttpTransport()
    private val jsonFactory = GsonFactory.getDefaultInstance()
    private val androidKeyInitializer = HttpRequestInitializer{
        //TODO: APPLICATION_IDはContextからの取得が望ましい？
        request ->
        // X-Android-Package ヘッダ
        request.headers.set("X-Android-Package", BuildConfig.APPLICATION_ID)
        // X-Android-Cert ヘッダ
        request.headers.set("X-Android-Cert", BuildConfig.SHA1_FINGERPRINT.replace(":", "")) //":"を除去する必要がある。
    }

    private val calendar = Calendar.Builder(transport, jsonFactory, androidKeyInitializer)
        .setApplicationName(BuildConfig.APPLICATION_ID) //TODO: APPLICATION_IDはContextからの取得が望ましい？
        .build()
```
### APIキー、パッケージ名、フィンガープリントの管理方法
- APIキー管理（local.properties → BuildConfig）
**local.properties**
``` properties
GOOGLE_API_KEY = XXXXXXX
```
**build.gradle.kts(Module :app)**
```kts
val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()){
    localProperties.load(FileInputStream(localPropertiesFile))
}

android {
     buildFeatures{
        buildConfig = true
    }
    defaultConfig {
        buildConfigField("String", "GOOGLE_API_KEY",
            "\"${localProperties.getProperty("GOOGLE_API_KEY", "")}\"")
    }
}
```
→ こうすることで、ソースコードにAPIキーを直接書かずに管理できます。

- フィンガープリント（SHA-1）の管理（BuildConfig）
**build.gradle.kts(Module :app)**
```kts
android {
    buildFeatures {
        buildConfig = true
    }

    buildTypes {
        debug {
            buildConfigField(
                "String", 
                "SHA1_FINGERPRINT", 
                "\"XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX\""
            )
        }
        release {
            buildConfigField(
                "String", 
                "SHA1_FINGERPRINT", 
                "\"YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY:YY\""
            )
        }
    }
}
```
→ Debug・Releaseそれぞれ別の証明書を使う場合にも対応できます！

## 注意点
- BuildConfigへのキー定義はAPKに埋め込まれるので、APIキーにはIP制限やアプリ制限が必要。
- セキュリティをさらに高めたい場合、Android Keystore、API Gatewayの導入や、サーバー経由でAPIを叩く方法があるらしい。

## まとめ
- GCP側でAPIキーに「パッケージ名＋フィンガープリント」の制限をかける
- Android側ではHTTPリクエストヘッダーに正しく情報を載せる
- APIキーやフィンガープリントはコードに直書きしない方法。(**APKの逆コンパイルによってKeyがバレる可能性はあり。**)