OmniAICreator/Qiita-1.07M huggingface dataset:
DDDとマルチデバイスを意識してアプリケーションのアーキテクチャを考えて見た

# 背景

去年会社でドメイン駆動設計（DDD）の勉強会をしましたが、現在動いているサービスにすぐ活かすことが難しそうでした。（そして会社で使っているRailsの場合は適用しにくい部分もありました。）

この勉強会の後、クリーンアーキテクチャについても学びましたので、両方の考え方をあわせてゼロからアプリケーションを作った場合はどうなるかを考えてみました。


# 前提知識

DDDとクリーンアーキテクチャの詳細については以下に書いてある本などを読むといいですが、今回考えたアーキテクチャで特に意識したことを簡単にまとめます。

## DDD

- ドメイン（アプリケーションの対象となる領域）の知識に焦点を当て設計をする
- ドメインに必要な知識と必要のない知識を考える
- ドメイン知識の関係を整理する
- ドメインのルールとアプリケーション（UIやユーザーが使う機能など）を分離する

詳細は [ドメイン駆動設計入門](https://www.amazon.co.jp/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E5%85%A5%E9%96%80-%E3%83%9C%E3%83%88%E3%83%A0%E3%82%A2%E3%83%83%E3%83%97%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E3%81%AE%E5%9F%BA%E6%9C%AC-%E6%88%90%E7%80%AC-%E5%85%81%E5%AE%A3/dp/479815072X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=) がオススメです。

## クリーンアーキテクチャ

- UIやビジネスロジックの分離
    - 同じアプリケーションを複数のデバイスで使えるようにするためにも必須
    - 扱っているデータや主な機能が同じのため、処理を共通化したい

詳細は [Clean Architecture](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3&qid=1621572874&s=books&sr=1-1) がオススメです。


# 課題

今回考えたアプリケーションは、Music Player（音楽再生アプリケーション）です。

私は音楽が大好きで、自分のiTunesライブラリを徹底的に管理していますが、iTunesはアップデートがある度に苦しんでいます…
エンジニアなので気に入らなかったら自分で作ればいいじゃん、という決意に至りました。

:warning: 実際にはまだ作っていませんが、こういうアーキテクチャにすればいいのではないかと考えています。

## 要件

まずは、どういう機能が欲しいかを考えました。

- ライブラリにある音楽を再生する
- 曲の関連情報（曲名、アルバム、アーティストなど）を管理する
- プレイリストを管理する
- 曲の細かい情報を関連する（ムード、作成者、タイアップ、国、言語など）
- 自動プレイリストを管理する
- 曲を検索する
- カバー曲を管理する
- アーティスト情報を管理する
- ライブ情報を管理する
- 歌詞を管理する

## ドメイン

要件が決まると、それらを実現するためにどの知識が必要かを考えることで、ドメインが決まります。
上の要件を満たすために以下のドメインオブジェクトが必要だと考えました。

> 曲、アーティスト、アルバム、プレイリスト、ジャンル、ムード、作曲者（人物）、タイアップ、国、言語、PV、ライブ、活動エリア（場所）、歌詞、メディア、フォーマット、職能、ライブハウス

:information_source: 今回は自分の課題を解決するためのアプリケーションを作りますので必要の知識と必要のない知識を判断するのは楽でしたが、仕事で作っているプロダクトではここが一番大変でしょう…

そしてドメインオブジェクトの関係を表すとこちらになります。

![MusicPlayer_domain.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553103/94c41f6e-7d8b-fa9b-db7c-0bf9b121bd21.png)


:information_source: 矢印の向きは親から子になっていますので、「A→B」の場合はオブジェクトAはオブジェクトBの情報を持っている
:information_source: E = エンティティ、V = 値オブジェクト

## アーキテクチャ

私はまだPCで音楽を聴くことが多いですが、外出している時もスマホでも聴きますので、今回作るアプリケーションは最初からマルチデバイスを考えないといけません。そうなると、フロントエンドとバックエンドの分離はまず必要です。

次に考えたのはドメインの知識を持つモデルの使い方です。ビジネスロジックなどのドメイン知識がエンティティに入りますので、インターフェイスであるフロントエンドのためにこちらのロジックが一切入っていないDTOのようなものがあるといいです。バックエンドではモデル（エンティティ）を共通で使用し、フロントエンドではDTOを共通に使います。

最後に、裏で定期的に動かす処理について考えてみました。まだ具体的な機能は思い浮かんでいませんが、もし必要になった場合はバックエンドの共通モデルが使える状態にしておきます。

まとめておきますと、以下の図にようになります。

![MusicPlayer_architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553103/a19e9ac6-db30-2cda-bed7-e2bd24576dc5.png)

# 最後に

以上で全体のアーキテクチャになります。実際に作り始めると他の課題は出てくると思いますが、基本的にこんな感じに進めればいいかなと思います。

既存のアプリケーションでアーキテクチャを大きく変更するのは大変なのでそこまで色々考える機会は仕事で少ないですが、新規のアプリケーションに最近勉強した手法や考え方を活かすのを考えて面白かったです。
CodePenで面白そうなテンプレート8選（ブラウザゲーム編）

#CodePenとは
CodePenはHTML、CSS、JavaScriptのコード入力画面と、その結果が表示される画面が一体で操作できるサービスです。
多くの作者がテンプレートを公開していて、自分の環境にフォークして使えます。

今回自分でWebアプリを作るにあたり参考にさせていただこうと調べた中で面白かったものを挙げていきます。

https://codepen.io/topics/


#お気に入りのGames
Gamesカテゴリの説明文は「From tic-tac-toe to clever 3D braintwisters to wacky platformers」とのこと。
「tic-tac-toe」は〇×ゲームのことだそう。


2021/5/21時点でSearch画面に表示された順に30個を調べてみました。（Like数順にソートする方法を知っている方、教えてくださいm(__)m）
個人的に直近で使えそう・使いたいものに◎をつけています。
**凄いな、と思ったものは太字にして上位に持ってきてます。**

https://codepen.io/Gthibaud/pen/ryQRYP

**↑　こんなのもう完全にスーファミ時代のゲームじゃん！　スタート画面からして完全にテレビゲーム。**

https://codepen.io/dissimulate/pen/CqIxk

**↑　こんなのまで作れるのか！　自機のボールが矢印キーでダンジョンを移動する。段々自機がマリオに見えてくる。**

https://codepen.io/juliangarnier/pen/BsIih

**↑　チェスゲーム。攻守交替で上下が回転する。あたりまえだけどちゃんと駒をルール通りに進めて駒を取れる。**

https://codepen.io/bsehovac/pen/EMyWVv

**↑　ルービックキューブ。どの列をどっち側に回すのか矢印キーでやるのかな、と思ったらカーソルで掴む、だった。まじか。**

https://codepen.io/ste-vg/pen/ppLQNW

↑　移動するオブジェクトをタイミングに合わせてクリックする系。段々と積み木が積みあがるのでついやりこんでしまいそう。

https://codepen.io/natewiley/pen/HBrbL

↑　◎　神経衰弱。今回作るWebアプリの元ネタに良さそうかな。

https://codepen.io/CaioPaiola/details/GFpuK

↑　懐かしすぎか！　蛇が餌を食べると自分の体が伸びていくゲーム。

https://codepen.io/cathydutton/pen/ldazc

↑　アルファベットを規定数以内にクリックして英単語を明らかにするゲーム。残り時間が棒人間の首吊りイラストが完成されるまで、という表現でクリスチャン・ベール主演の映画「マシニスト」を思い出したのですが、[HANGMANという遊び](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%B3%E3%82%B0%E3%83%9E%E3%83%B3_(%E3%82%B2%E3%83%BC%E3%83%A0))が元々あるんですね。


#おわりに
完全にスーファミレベルで驚きました。。。

おすすめのテンプレートがありましたら是非コメントで教えてください。m(__)m
gitのvendorファイルを gitignoreする

これは、gitにてvendorファイルが、ビックリするほど多くgitに反映されてしまった時に対処した方法です。
あまりない事のようですが、またこの問題が出てきそうなので、メモとして残します。


※他のファイルをignoreしたい場合は、今回の `vendore` をご自身の無視したいファイル名に変えて記述してください。
また、もし既にコミットしてしまった場合は、別のやり方をしてから、この作業になりますので、まずは今どの状態なのかを確認してから行いましょう。
　


さて、結論としては

VScodeの【ignoreファイル】の一番下に、無視したいファイル名
今回は `vendor`(不要なファイル名)と記述し、gitをリロードすればOKです。
（リロードできない場合は、gitを一度消して再度立ち上げるでもOK）



なのですが、私はVScodeの一番下に　`vendor`を記述
更にターミナルにて

```
% git rm --cached ファイル名 （今回はvendor）
```

と実行しました。
これでも結果は一緒でしたが、より簡単なのはVSコードに記述するだけの方法でした。

　

今回は　`vendor`　ファイルでしたが、本来であれば　`vendor` ファイルはignoreにそもそも記載されているもので、何かしらの影響で、この記載が外れてしまっていたようです。




とても初歩の事ですが、忘れてしまわないようにメモとして残しました。
STM32G031の独自のソフトウェアシリアルでHello Worldと出力 1200bps


目的
出来そうだから作ってみた

とりあえず1200bps そのまま動いた　ウエートと832us


```c++

#define DW   digitalWrite

//104 9600
//208 4800
//416 2400
//832 1200

#define UART_DELAY 832 //  1/9600
//#define UART_DELAY 96 //  1/9600

//#define swdclk   PA14 // 8pin
//#define swdio    PA13 // 7pin
#define swdclk   PA12 // 6pin
#define swdio    PA11 // 5pin
//#define t_led1   PA12 // 6pin
//#define t_led2   PA11 // 5pin
#define en       PA0  // 4pin
#define in7      PB7  // 1pin

//仮想シリアルへの一文字出力 9600bps
int pc_putc(char ch) {

  DW(in7, HIGH);
  DW(in7, LOW);//START
  delayMicroseconds(UART_DELAY);

  for(int ii=0;ii<8;ii++){
    DW(in7, (ch>>ii)&1  );
    delayMicroseconds(UART_DELAY);
  }//for

  DW(in7, HIGH);//Stop
  delayMicroseconds(UART_DELAY);

  return(0);

}//pc_putc


void setup() {

  //仮想シリアルの出力ポート

  //ポートをhiにする 初期化
  pinMode(in7, OUTPUT);
  DW(in7, HIGH);

}//setup

char *ss1; //文字のポインター
int ii; //ループカウンター


//無限ループ
void loop() {

  ss1="Hello World !\r\n";
  ii=0; //ループカウンター
  while(ss1[ii]!=0){

    //一文字出力
    pc_putc(ss1[ii]);ii++;

  } //while

  //1秒の待ち
  delay(1000);

}//loop



```

1200bps

![soft_serial_test_031_1200.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/46aaf39c-8bc3-0200-2f29-c853d7b75595.jpeg)
iphoneの画面が勝手に拡大される問題

**文字を入力するときに勝手に画面が拡大されるときの対処**


iphoneで、テキストボックスに文字を入力するとき、
勝手に画面が拡大され、手動でいちいち画面倍率を戻さないといけないという事象。


これは、iosの親切で、
文字が小さくて見にくいから自動ズームしてくれており、不具合ではありません。


###対処法

```ruby:qiita.rb
style="font-size:16px;"
```

文字サイズをあらかじめ16px以上に設定しておくと、
ズームされません。
サイトのタイトルのみをスクレイピングするツール

機能一蘭

・A列のURLにサイトトップページのURRLを記入する。
・スクリプトを実行するとメディアに対応したタイトルが上から記入される。
・B列がすでに埋まっている時は飛ばし、B列内で空白のセルを探しそこからスタートする。


```
function myFunction() {
  //1. 現在のスプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  //2. 現在のシートを取得
  var sheet = spreadsheet.getActiveSheet();
  //3. 指定するセルの範囲（A列）を取得
  const lastRow = sheet.getLastRow();
  for(let i = 2; i <= lastRow; i++) {
  　　//A列を取得
    var range = sheet.getRange(i,1);
  　　//制限時間対策のためB列に値が入っているときはスキップする
　　if(sheet.getRange(i,2).getValue() !== ""){
　　continue;
　　}
    var value = range.getValue();
    var response = UrlFetchApp.fetch(value);
    var html = response.getContentText('UTF-8');

  /////③タイトルタグの抽出/////
  　　//開始文字列の指定
    var start_str = "<title>";
    var start_num = html.indexOf(start_str)+start_str.length;

  //終了文字列の指定
    var end_str = "</title>";
    var end_num = html.indexOf(end_str);

  //タイトルタグをスクレイピング
    var title = html.substring(start_num, end_num);

  //結果の出力
    Logger.log(title);
  //B列にタイトルを記入
    sheet.getRange(i, 2).setValue(title);
  } 
}
```

メディアの競合リサーチの際に役に立つと思うので使ってみてください。
他にも作成していきます。
変数が代入できない

結論
-

createアクションをendで終わらせる前に次のshowアクションを書いてしまった。
実際は以下。
createアクションのendを描く前にshowとdestroyのアクションを書いてしまい、
うまくshowアクションとdestroyアクションが機能しなかった

class CardsController < ApplicationController
  before_action :authenticate_user!
  def new
    redirect_to root_path if user_signed_in? && current_user.card
  end

  def new
    @card = Card.new
  end

  def create
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
    card = Card.find_by(user_id: current_user.id)
    customer = Payjp::Customer.retrieve(card.customer_token)
    @card = customer.cards.first

    if @card.save
      redirect_to root_path
    else
      render "new"
    end


    def show
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      card = Card.find_by(user_id: current_user.id)
      customer = Payjp::Customer.retrieve(card.customer_token)
      @card = customer.cards.first
    end

    def destroy
      card = Card.find_by(user_id: current_user.id)
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      customer = Payjp::Customer.retrieve(card.customer_token)
      customer.delete
      card.delete
        redirect_to action: "new"
    end
  end
end
高度な数式 ⇒ チェックボックス数式での基本的なロジックの使用

Create a validation rule indicating whether an opportunity has a closed date in the past.
Your number-crushing sales team has so many deals in the pipeline, you’re starting to see occasional problems with data quality. Namely, sales reps are forgetting to update the close date to a date in the future. Make a formula that requires an opportunity to have a CloseDate of today or any future date. Hint: this formula should reference the hidden IsClosed checkbox field on the Opportunity object, and you will be creating the formula as an Opportunity validation rule.

The validation rule should be on the Opportunity object
The validation rule should be named 'Close_Date_Alert'
The validation rule should fire if IsClosed is not selected and CloseDate is yesterday or earlier
The validation rule should display the error 'Hey McFly, unless you are planning to go back in time, please update your close date' at the top of the page when triggered

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634241/d57149a8-06eb-475f-b50c-cf0e60011734.png)
NOT(IsClosed) && CloseDate < TODAY()
【Heroku】「 We're sorry, but something went wrong.」アプリが開けないときの対処法

#概要
Herokuでアプリを公開して問題なく動作していたのですが、突然以下のように We're sorry, but something went wrong. というエラーが出てアプリが動かなくなりました。その対処法を記載しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/78bfb391-a9be-19cc-46d2-43dee13f9f36.png)

#環境
ruby 2.6.5
Rails 6.0.3.5
heroku 7.51.0

#試したこと①
herokuコマンドでログを確認。

```
% heroku logs -t
```
しかし、エラーは書いていない。
(-t オプションをつけるとストリーミング状態のログを見ることができる)


#試したこと②
We're sorry, but something went wrong.で検索すると

```
% heroku run rails db:migrate
```
で解決できたという情報が多々あったので実行。

```
ActiveRecord::StatementInvalid: Mysql2::Error: User 'b6eb5abd3faa98' has exceeded the 'max_questions' resource (current value: 3600)
```
このようなエラーを見つけました。

どうやらHerokuの無料枠だとデータの読み込み回数が時間によって決まっている為、出てしまうエラーでした。
確かに直前にたくさんアプリ動かしていました、、、

しばらく放置し、以下のコマンドでブラウザでアプリを開くと

```
% heroku open
```
問題なく読み込めました。





参考記事
[【Heroku】Ruby on Rails+MySQLで作ったファイルのデプロイ -blog](https://yasagori-programing.hatenablog.jp/entry/2019/03/30/190000)
[【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】 -Qiita](https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39)
[【Rails】本番環境デプロイでよく使うコマンド集！AWS/unicorn/nginx/Capistrano使用 -Qiita]
(https://qiita.com/15grmr/items/7ad36caa82a0fa27c4bd)
moleculeでのfilter pluginの利用方法

# 概要
moleculeの処理でfilter pluginを利用する際にはまったことを忘れないように残す。

#TL;DR

roles配下のフォルダ構成(関連個所のみ)

```
roles
 ├moleule
 |  └default
 |     ├molecule.yml
 |     ├playbook.yml 　　※ここで使いたい
 |     ├verify.yml　　　　※ここで使いたい
 |     └filter_plugin
 |        └custom_filter.py　※これを使いたい
 ├tasks
 └defaults
```

molecule.ymlのprovisionerに以下の設定を追加する

```molecule.yml
provisioner:
  env:
    ANSIBLE_FILTER_PLUGINS: ./filter_plugin
```
filter pluginを利用するplaybook(playbook.ymlやverify.yml、等)で「gather_facts: yes」となっていること
※ANSIBLE_FILTER_PLUGINSはgather_factsによって読み込まれるため。

```playbook.yml
- hosts: target
  gather_facts: yes
```

# 参考
moleculeマニュアル
https://molecule.readthedocs.io/en/latest/configuration.html
【Android】SharedFlowを使ってEventBusを作る

## 前置き
いいね処理、お気に入り処理などの、画面間をまたいだイベント送信を行いたい場合、これまでは[EventBus](https://github.com/greenrobot/EventBus)や[otto](https://github.com/square/otto)を使っていた方が多いと思います。

ですが、SharedFlowを使えばライブラリを使わずとも自前で簡単に実装できるので、備忘録がてらサンプルを書いておきます。

実はSharedFlowの公式ページにもサンプルが書いてあります。
今回はそれをちょっとだけ弄っただけです:pray:

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/

## Sample

``` CoroutineEventStore.kt
class CoroutineEventStore {
    companion object {
        private var instance: CoroutineEventStore = CoroutineEventStore()
        fun getInstance() : CoroutineEventStore {
            return instance
        }
    }

    // Eventはこれを継承すること
    interface CoroutineEvent

    private val _events = MutableSharedFlow<CoroutineEvent>()
    val events = _events.asSharedFlow()

    suspend fun post(event: CoroutineEvent) {
        _events.emit(event)
    }
}

data class HogeEvent(val data: **): CoroutineEventStore.CoroutineEvent
```

## 使い方

### 送信
```
CoroutineEventStore.getInstance().post(hogeEvent)
```

### 受信
```
**Scope.launch {
  CoroutineEventStore.getInstance().events
    .mapNotNull { if (it is HogeEvent) it else null }
    .collect {
      // 受信時の処理を書く
    }
}
```

特に受信処理は各EventクラスごとにUseCaseなどで分けてあげると使いやすいと思います。
とっても簡単にEventBusが作れるのでありがたいですね:sunny:
Arduino UNOの独自のソフトウェアシリアルでHello Worldと出力 1200bps


目的
出来そうだから作ってみた




とりあえず1200bps そのまま動いた　ウエートと832us
指摘があったので直してみた 5/21 指摘、ありがとうございました。

いろいろ
STM32G031で動くがどうか実験的に作ってみた。
1200bps程度だとGPIO(実行時間)の影響をほとんど受けないので論理値のウェート時間で動いた。
基本シリアル通信は、同期がずれると最終ビットが影響を受ける。
UART_DELAYの値を1足したり引いたりするとうまくいくかも
Tera Termの場合は、1100bpsとか1300bpsとか設定出来るので試すといいかも
基本、オシロがなくても調整できる方式である内部タイマーを使っている。
オシロ持ってない人の方が普通


```c++


#define DW   digitalWrite

//104 9600
//208 4800
//416 2400
//832 1200

#define UART_DELAY 832 //  1/9600
//#define UART_DELAY 96 //  1/9600

//仮想シリアルへの一文字出力 9600bps
int pc_putc(char ch) {
  
  DW(13, HIGH);
  DW(13, LOW);//START
  delayMicroseconds(UART_DELAY);

  for(int ii=0;ii<8;ii++){
    DW(13, (ch>>ii)&1  );
    delayMicroseconds(UART_DELAY);
  }//for

  DW(13, HIGH);//Stop
  delayMicroseconds(UART_DELAY);

  return(0);

}//pc_putc


void setup() {
  
  //仮想シリアルの出力ポート
  
  //ポートをhiにする 初期化
  pinMode(13, OUTPUT);
  
}//setup

char *ss1; //文字のポインター
int ii; //ループカウンター


//無限ループ
void loop() {
  
  ss1="Hello World !\r\n";
  ii=0; //ループカウンター
  while(ss1[ii]!=0){
  
    //一文字出力
    pc_putc(ss1[ii]);ii++;
    
  } //while

  //1秒の待ち
  delay(1000);

}//loop



```
ISO8601について

## 概要
ISO8601とは「**[^1]ISOで定められた日付と時刻の表記に関する国際規格**」のこと。

## ISO8601の形式

日付と時刻を「**T**」で区切ります。

[^2]UTCの場合、秒の後に「**Z**」を付け、その他のタイムゾーンの場合、秒の後に「**±時間**」を表記して、タイムゾーンごとの違いを表します。

> 例: 2021年5月21日 14:00

- UTC: 2021-05-21T14:00:00Z
- [^3]JST: 2021-05-21T14:00:00+09:00

## 2つの形式

形式には「**基本形式**」と「**拡張形式**」の2つがあり、表記内に「**区切りを入れて見やすくしているか**」という違いがあります。

- 基本形式: YYYYMMDDThhmmss
- 拡張形式: YYYY-MM-DDThh:mm :ss

基本形式と拡張形式の混在はダメなので、どちらかに統一する必要があります。

### 年月日と時間の形式
- 年: YYYY
- 月: MM（01から12）
- 日: DD
- 時: hh（00から24）
- 分: mm（00から59）
- 秒: ss（00から59（閏秒に60をとる））

### 年月日で使用出来る形式
- YYYYMMDD
- YYYY-MM-DD
- YYYY-MM（YYYYMMは使用できない）
- YYYY

### 時間について
「00:00」は日の初め、「24:00」は日の終わりを表します。

例:「2021-05-21T24:00:00Z」と「2021-05-22T00:00:00Z」は同じ日付

### タイムゾーンの形式
- +hh:mm
- +hhmm
- +hh

[^1]: 国際標準化機構。国際的な標準の「国際規格」を策定している。
[^2]: 協定世界時
[^3]: 日本標準時。UTCより9時間進んでいる
Go言語でJSONオブジェクトの順序を維持したままスライスに変換する

## はじめに

`encoding/json` パッケージを利用してJSONオブジェクトをパースする際に、JSON文字列の順序の通りのスライスに変換する方法をご紹介します。

## TL;DR

- [json.Unmarshaler](https://pkg.go.dev/encoding/json#Unmarshaler)を実装して[Decoder.Token()](https://pkg.go.dev/encoding/json#Decoder.Token)で順次変換する
- 雑にパースするためのライブラリを作ったのでご利用ください ([reference](https://pkg.go.dev/github.com/kamiaka/go-jsonutil))
- 新しく作るものではではJSONオブジェクトに順序をもとめてはいけない

## モチベーション

既存のAPIレスポンスなどでキーの順序に意味のあるJSONオブジェクトを扱うため、スライスとしてパースする機会がありました。

当初は `map[string]*Value` のようなmapとしてパースすることを検討しましたが、Goのmapは順序が保証されない(ランダムにソートされる可能性がある)ためスライスに変換後、ソートするなどしていましたが、ソート可能なキーが存在しないケースがありました。

### レスポンス例

```json
{
  "value1": 42,
  "value2": 99
}
```

上記のようなフォーマットで順序に意味がありました。

## `json.Decoder` を利用する

[json.Decoder](https://pkg.go.dev/encoding/json#Decoder) の `Token()` メソッドで順次処理することで、JSON文字列の順序通りに扱うことができます。

```go
// bs := []byte(`{"value1":42,"value2":99}`)
decoder := json.NewDecoder(bytes.NewReader(bs))
for {
	token, err := decoder.Token()
	if err != nil {
		if err != io.EOF {
			fmt.Println(err)
		}
		return
	}
	fmt.Printf("%T: %v\n", token, token)
}
```

Output

```
json.Delim: {
string: value1
float64: 42
string: value2
float64: 99
json.Delim: }
```

## とはいえデリミタの判定とか毎回するのは面倒...

雑に利用するためのパッケージをつくったのでぜひご利用ください

[github.com/kamiaka/go-jsonutil](https://pkg.go.dev/github.com/kamiaka/go-jsonutil)

### 使い方

```go
func UnmarshalEntries(json []byte, resolver func(key string, data []byte) error)
```

UnmarshalEntriesでは第1引数で受け取ったJSONをパースして、エントリごとに第2引数にキーと値のペアを渡します。(エラーが返った場合中断してエラーを返します)

```go
type IntEntry struct { Key string; Value int }
var entries []*IntEntry
jsonutil.UnmarshalEntries([]byte(`{"a":1,"b":2,"c":3}`), func(key string, data []byte) error {
  entry := &IntEntry{
    Key: key,
  }
  entries = append(entries, entry)
  return json.Unmarshal(data, &entry.Value)
})
```

json.Unmarshalerの実装内にて呼び出すことで、再帰的なパースも可能です。

```go
type IntEntries []*IntEntry

func (entries *IntEntries) UnmarshalJSON(bs []byte) error {
	return jsonutil.UnmarshalEntries(bs, func(key string, data []byte) error {
		entry := &IntEntry{
			Key: key,
		}

		*entries = append(*entries, entry)
		return json.Unmarshal(data, &entry.Value)
	})
}

type X struct {
	Foo     string     `json:"foo"`
	Entries IntEntries `json:"entries"`
}

func main() {
	var x *X
	err := json.Unmarshal([]byte(`{"foo":"Foo!","entries":{"a":42,"b":99}}`), &x)
	fmt.Printf("%#v, error: %v", x, err)
	// Output:
	// &main.X{Foo:"Foo!", Entries:main.IntEntries{(*main.IntEntry)(0xc00000c220), (*main.IntEntry)(0xc00000c2a0)}}, error: <nil>
}
```

詳しくは [ドキュメント](https://pkg.go.dev/github.com/kamiaka/go-jsonutil) をご覧ください。

## さいごに

あくまで既存APIのレスポンスなどでJSONオブジェクトの順序に意味があった際に順序をそのまま扱うためのものです。
ほとんどの言語において連想配列に順序はありません。
新規作成するAPIなどで順序に意味をもたせる場合は配列にするなどしてください。
LaravelのページネーションをVueで使う

Laravelのパラメータ付きURLのページネーションをVueで使う場合

```php:Controller.php
        //入力項目によりクエリ動的生成
        $q = Job::query();
        if (!empty($city_id[0])) {
            $q->whereIn('city_id', $city_id);
        }

        if (!empty($category_id[0])) {
            $q->whereIn('category_id', $category_id);
        }
        if (!empty($status_id[0])) {
            //dd($status_id);
            $q->whereIn('status_id', $status_id);
        }
        if (!empty($feature_id[0])) {
            $q->whereHas('feature', function ($query) use ($feature_id) {
                $query->whereIn('feature_id',$feature_id);
            });
        }
        //検索パラメータに戻す
        $params = array(
            'city_id' => $request->city_id,
            'category_id' => $request->category_id,
            'status_id' => $request->status_id,
            'feature_id' => $request->feature_id,
        );
		//appends
        $jobs = $q->with('User:id,name','Category','City','Status','Feature')->latest()->paginate(1)->appends($params);

```


```javascript:vue
<template>
<div>
<ul class="pagination">
<li v-for="(link, index) in jobs.links" v-if = "link.active === true" class="active"><a :href='link.url'>{{index}}</a></li>
<li v-else-if = "link.label === `&laquo; Previous`" v-show="link.url"><a :href='link.url'>前へ</a></li>
<li v-else-if = "link.label === `Next &raquo;`" v-show="link.url"><a :href='link.url'>次へ</a></li>
<li v-else><a :href='link.url'>{{index}}</a></li>
</ul>
</div>
</template>

```
IT用語〜セキュリティ〜【初心者向け】

#はじめに

今回も前回に引き続き、用語集です。ネットワークの基礎を引き続き学んでいます。
セキュリティに関する用語について書いていきます。

#IT用語

####DoS攻撃（ドスこうげき）

> DoS は Deny of Service の略。
>外部からの攻撃。ネットワークやサーバに対して膨大なデータを送りつけてサービスを妨害するやり方。
攻撃を受けた側は以下のようなことが起こる
・大量のアクセス要求によって正規のユーザーがアクセスできなくなる。
・サーバに負荷がかかりすぎてサーバが使用不能になる。
・クラウド上で従量課金制のサービスであれば高額な請求が発生する。

####DMZ(非武装地帯)

>DMZは DeMilitarized Zone の略。
>外部からの犯行への対策。
外部のネットワークと、内部のネットワークの中間に設置し中からも外からも覗くことができる。
外部に公開するために隔離した内部ネットワーク。仮に乗っ取られても、内部にまで影響が及ぶことがない。

####ファイアウォール

>主な機能として
・アクセス制限
・アドレス変換
・ログ収集
がある

>>・アクセス制限・・・アクセス制限を行うことで、悪意ある不正アクセスから社内のネットワークを守る。

>>・アドレス変換・・・プライベートアドレスとグローバルアドレスの変換を行い、社内のIPアドレスを隠す。

>>・ログ収集・・・ログ(通信の記録)の収集や監視を行い、原因特定に努める。

####ウイルス対策ソフト（アンチウイルスソフト）

>メールに添付されたウイルスなど内部からのウイルスは、ファイアウォールだけでは防ぎきれない。
そんな時に、PCにワクチン接種するような役割でウイルス対策ソフトがある。

####UTM(統合脅威管理)

>UTM は Unified Threat Managemen の略。
>アンチウイルス、不正侵入防御、Webコンテンツフィルタリングなど複数のセキュリティ機能が統合され、一元管理できる装置。
不正アクセスを防ぐためにファイアウォールを用意したり、コンピュータウイルス対策にウイルス対策ソフトを入れたり、個別で用意する必要がなく、まとめて管理してくれる。
メリットは、管理が楽なこと。
デメリットは単体の能力は単体製品の方が高い場合があること。

####HTTP

>HyperText Transfer Protocol（ハイパーテキスト・トランスファー・プロトコル）の略。
Webページの送受信に使われるWebサーバとの約束事。
HTTPでやりとりをするときは通信の内容が暗号化されず、通信の内容を覗かれるとバレてしまう。

####HTTPS

>HTTP over SSL の略。SSL は Secure Socket Layer の略。
>HTTPSは通信の内容が暗号化される。

#参考

「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
https://wa3.i-3-i.info/word165.html
AWS S3の大量オブジェクトの削除方法 <AWS S3 batch Operations>

## はじめに
サービスで使用している画像ファイルが8億オブジェクト以上ある中で、
一部サービス終了に伴い画像ファイルを削除する必要が発生し
大量にあるので、マネージメントコンソールからオブジェクトの削除を実施しても
完了しない・タイムアウトになることから安全かつ確実に削除する方法を実施したものを
まとめてみました。


## 前提
１つバケットに対して、フォルダごとに機能別にオブジェクト（画像）を管理している。

## やりたいこと
バケットの中のオブジェクトは現状サービスでも使われているものもあるので、
バックアップを取得して安全に削除を実施すること。

## 課題
大量のオブジェクトがあることから、マネージメントコンソールやawscliでは難しいこと。

## 解決策
大量のS3オブジェクトに対して一括でAPIを実行できるS3 batch Operatonsで、
削除するオブジェクトに対してタグを付け、念のためにバケット全体のバックアップで
オブジェクトのコピーを実施する。
（全体のバックアップはもしかしたら不要かもしれないが、削除するものだけバックアップをとる方がいい）

削除は上記でタグを付けたものに対して、ライフサイクルルールで削除を実施していくことで、
多少時間が掛かるが安全に削除を実施した。

## やってみる 
### step1.S3バケットのインベントリーレポートを取得
対象のオブジェクト情報を取得する必要があるため、インベントリー情報から取得をおこないました。


出力される「manifest.json」ファイルに中にオブジェクトの実際の情報がcsvファイルで書かれています。



### step2.削除するオブジェクトに対してタグ付けを実施
:thinking:タグ付自体はもし処理中にオブジェクトがアップされた場合は削除されるのを
防ぐためのものになりますので、特に気にしない場合はスキップしても問題ないかと思います。




### step3.バケット全体を別バケットにバックアップ（コピー）



### step4.ライフサイクルルールでオブジェクトを削除




### step5.バックアップ先のオブジェクトを削除

★★★近日アップデート
step4と基本は同じようにライフサイクルルールでの削除を実施


### 参考Link
https://dev.classmethod.jp/articles/try-amazon-s3-batch-operations/

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/batch-ops-iam-role-policies.html
Flutter をMacに導入

# はじめに

flutterの導入
macにインストールしてアプリ起動まで


## SDKのダウンロード
Flutter 公式ページ
https://flutter.dev/docs/get-started/install/macos

ここから最新のSDKをダウンロードして解凍したファイルを任意のディレクトリに配置
ここではホームディレクトリ配下とする

## pathを通す

flutterコマンドを使用できるようにするため、解凍したファイルのbinフォルダまでpathを通す

```
export PATH=$PATH:$HOME/flutter/bin
```

## 環境確認
flutterには環境構築に必要なものが入っているかを表示するコマンドが存在する

```
$ flutter doctor
```
上コマンドを叩く問題なければ以下の結果が表示される

```
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.3, on Mac OS X 10.15.6 19G73 darwin-x64, locale
    ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.56.2)
[✓] Connected device (1 available)

• No issues found!
```
これで問題が存在するときは[!][x]が表示されるので解消する

### 一部項目の対処方法
- Android toolchain & Android Studio

Android Studioのインストール

Android Studio > Preferences > Plugins
から「Flutter」と「Dart」をインストール

- Xcode

XcodeとCocoaPodsのインストール

- VSCode

VSCodeのインストール+拡張機能でFlutterプラグインを導入



## プロジェクトの作成

ここまできたら動かせる準備ができてると思うので
作業ディレクトリに移動して

```
$ flutter create my_app
$ cd my_app
$ flutter run
```
-  flutter create ${プロジェクト名}

プロジェクトの枠組みが作成される。
上コマンドでは作業ディレクトリにmy_appが作成される
-  cd ${プロジェクト名}

コマンドで起動するにはFlutterプロジェクトに移動する必要がある
-  flutter run

起動状態にあるデバイスで実行が行われる
起動状態デバイスが複数存在するときは選択状態待機になる
※VSCode上では右下からデバイスを選択してからrunするほうがよい
vue-awesome-countdown でカウントダウンタイマーを設置する

# はじめに
Vue で使えるカウントダウンタイマーのコンポーネント "vue-awesome-countdown" を紹介
https://vac.js.org/

## 前提
* 一定の日時まで自動的にカウントダウンを行うタイマーを設置する
* Vue のバージョンは 2.6.0+ とする

# 使ってみる
## 基本的な使い方
1. 導入する

    ```console
    $ npm install vue-awesome-countdown
    ```

1. 使えるようにする

    ```js
    import VueAwesomeCountdown from 'vue-awesome-countdown'
    
    Vue.use(VueAwesomeCountdown)
    ```

1. 使う

    ```vue
    <countdown
      :start-time="new Date()"
      :end-time="new Date(2022, 0, 1, 0, 0, 0)"
      auto-start
    >
      <template v-slot:process="{ timeObj }">
        <p>2022年まであと{{ timeObj.d }}日{{ timeObj.h }}時間{{ timeObj.m }}分{{ timeObj.s }}秒</p>
      </template>
    </countdown>
    ```

`process` スロットのスコープ内に存在する `timeObj` に時間の情報が入っている
すると↓こんな感じでリアルタイムでカウントダウンするタイマーが表示される（静止画ですがちゃんと秒読みします）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16478/7c411030-f451-b6a6-2791-22da4ea8fc6e.png)

## カウントダウン終了時に表示する内容を変更する
`finish` スロットに、タイマーが 0 になったときに表示する内容を記述しておくことができる（`process` スロットの内容の代わりに表示される）

```vue
<countdown
  :start-time="new Date()"
  :end-time="new Date(2022, 0, 1, 0, 0, 0)"
  auto-start
>
  <template v-slot:process="{ timeObj }">
    <p>2022年まであと{{ timeObj.d }}日{{ timeObj.h }}時間{{ timeObj.m }}分{{ timeObj.s }}秒</p>
  </template>
  <template v-slot:finish>
    <p>A HAPPY NEW YEAR !!</p>
  </template>
</countdown>
```
Sourcetreeでのスカッシュマージ



# スカッシュマージ

### 1.1 スカッシュマージってなーに？
端的に言うと「複数のコミットを1つのコミットとして処理をする。」ことのようです。
複数のコミットを、改めて１つのコミットとして扱うことができる便利機能のようです。
コメントも新しく整理して書き込めるみたいで、履歴が綺麗になりますね。有難い。


### 1.2 sourcetreeでのスカッシュマージのやり方

こちらの記事が分かりやすかったです。

https://qiita.com/ryounagaoka/items/7c129e98a7f81c507a61
【全くの初心者がAWSを学ぶ前に知っておきたいAWS学習手順】

「全くの初心者がAWSを学び始めて陥る罠」

・”VPC、EC2、AZ、API、RDS、IGW、AMI、Snapshot...などなど、横文字が多くて意味が分からない。”

・”動画講座を観て、指示通りにやってみたが画面が同じようにならず、不安になる。”


以上のような気持ちにならなかったでしょうか？
少なくとも私はなりました笑


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜


「私が実際に行った手順」

・”VPC、EC2、AZ、API、RDS、IGW、AMI、Snapshot...などなど、横文字が多くて意味が分からない。”

→まずは〈　”概要理解”　〉から！


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

概要理解→ハンズオン（実際に手を動かす）→ハンズオンで作っているものとは別に設定やサーバー構築を行う→使っているうちに単語を覚える（テスト形式でインプットする）

流れです。


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

「概要理解」

個人的に分かりやすかったYouTube動画３選

〈其の一〉
動画名：Web/IT用語27選！超カンタンに解説してみた　【クラウドとは？サーバーとは？データベースとは？】
提供者：セイト先生のWeb・IT塾　さん
URL：https://youtu.be/qIRtN61gMfQ

〈其の二〉
動画名：サーバーとは？【分かりやすい解説シリーズ #6】【プログラミング】
提供者：徳田啓　【プログラミング学習チャンネル】　さん
URL：https://youtu.be/cxX0r-ylLSE

〈其の三〉
動画名：【AWS 入門】基本ネットワーク構成をマスターしよう！
提供者：クライン　【KLEIN】　さん
URL：https://youtu.be/7J_crzU5KP8



おおよその概要が掴めましたら（単語はうろ覚えで良し）、
次は　”CloudTech”か”Udemy”で実際に手を動かしながら、学びましょう。
↓


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

「ハンズオン」



・CloudTech（インフラメインのエンジニアの「くろかわ　こうへい」さんがリリースした「会員制コミュニティ付きのAWS初学者を導く体系的な動画学習サービス」）で学ぶのが１番のおすすめです！

URL：https://aws-cloud-tech.com/?pa=RM


理由：月額制で安い。（月２,９８０円）他の教材が数万円する中、破格で受けられる。会員コミュニティがあるため、タイムリーに質問できる。持続しやすい。丁寧な概要説明、テスト機能があり、AWS認定の資格習得を目指す人にマッチします。









・次に　Udemy（オンラインで専門的指導を受けられるサイトです）で

〈其の一〉
講座名：AWS：ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得
講師：山浦　清秀　さん
URL：https://www.udemy.com/course/aws-and-infra/
一言：とりあえず、AWSでサーバー構築してみたい人向け。初心者向き。

〈其の二〉
講座名：これだけでOK！ AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座（SAA-C02試験対応版）
講師：Shingo　Shibata　さん
URL：https://www.udemy.com/course/aws-associate/
一言：SAA（AWS認定の資格）を取りたい。就活生向き。

上記２つは、セールで安くなっている時があるので、セール時（約1000〜2000円）におすすめします！



〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜


・”動画講座を観て、指示通りにやってみたが画面が同じようにならず、不安になる。”

→AWSは日々、細かなルールやグラフィックが変わります！！焦らずに現在、何をしているのかを頭に入れつつ進めましょう。分からないことがあったら、すぐ質問するのではなく、一度ググってみましょう。（エンジニアの仕事の大半がこれ）

→ハンズオンの学習は極力一気に進めましょう。時間を置くとより料金が発生したり（AWSは従量課金制）、ターミナルのタイムアウト（料金やタイムアウトは些細なことですが）、どこまでやったか分からなくなるなどを防ぐためです。


一緒にがんばりましょう！！
CodePenで面白そうなテンプレート5選（連絡先ページのデザイン編）

#CodePenとは
CodePenはHTML、CSS、JavaScriptのコード入力画面と、その結果が表示される画面が一体で操作できるサービスです。
多くの作者がテンプレートを公開していて、自分の環境にフォークして使えます。

今回自分でWebアプリを作るにあたり参考にさせていただこうと調べた中で面白かったものを挙げていきます。

https://codepen.io/topics/


#お気に入りのEmails
Emailsは「Design like it's 1999!」と説明されてるんですが、これは・・・どういう意味なんでしょう・・・？
わかる方、コメントで教えてください。m(__)m

追記）[「party like it's 1999」が「まるで今日がこの世の最後の日のように弾けたパーティを行う」ということらしい](https://bookworm.hatenablog.com/entry/2019/03/11/213413)ので「Design like it's 1999!」は同様に「弾けたデザインしようぜ！」ってことかしら。


投稿されているのを見た感じ、連絡先ページのデザインという感じかな、と思ってます。

2021/5/21時点でFeaturedされた順に18個を調べてみました。（Like数順にソートする方法を知っている方、教えてくださいm(__)m）
個人的に直近で使えそう・使いたいものに◎をつけています。

https://codepen.io/reallygoodemails/pen/bWXvOp

 ↑ イルミネーション表示されたメニュー看板のよう。こんな表現できるんだ・・・


https://codepen.io/drew_mc/pen/oXdZNR

↑　チャットアプリで会話してる風。

https://codepen.io/vogunal/pen/JoamjJ

↑　領収書ページ風。

https://codepen.io/zavoloklom/pen/qEVqzx

↑　正しくEmailのような表示。


https://codepen.io/LeftyDesigner/pen/BdDza

↑　外国のニュース記事風。


#おわりに
今回作ろうとしているのは動的なアプリなので、今はそこまで必要なかったですね。。
ただ今後静的なサイトを作るときに参考にできそうです。

おすすめのテンプレートがありましたら是非コメントで教えてください。m(__)m
bash 変数展開

毎回ググるのでまとめる

- 最初の検索文字列で分割したい
  - 前 `${v%%=*}` ( `aa=bb=cc` を `aa` に )
  - 後 `${v#*=}` ( `aa=bb=cc` を `bb=cc` に )
- 変数があるときにだけオプション指定
  - `${opt:-  --option="$opt" }`
macOSでのApache Kafkaセットアップメモ

Apache Kafkaについては更新スピードはやいので
基本Docを参照するのがよい

-> [Apache Kafka](https://kafka.apache.org/intro)


## Apache Kafkaのインストール

Macでインストール

```
brew install kafka
```

現時点でインスコされるのはv2.8.0

- `/usr/local/bin/` にkafka関連のコマンドがインスコされる
- `/usr/local/etc/kafka/` にコンフィグファイルなどが保存される

## 設定の変更

ホームディレクトリに適当なディレクトリを作って使うファイルだけコピー

```
mkdir kafka
mkdir kafka/config
cp /usr/local/etc/kafka/server.properties ./kafka/config
cp /usr/local/etc/kafka/zookeeper.properties ./kafka/config
```

ログファイルなど保存場所も変更したいので保存先ディレクトリを作成

```
mkdir kafka/data
mkdir kafka/data/zookeeper
mkdir kafka/data/kafka-logs
```


`./kafka/config/server.properties` の `log.dirs` を変更

```
log.dirs=/Users/yhirai/kafka/data/kafka-logs
```

`./kafka/config/zookeeper.properties` の `dataDir` を変更


```
dataDir=/Users/yhirai/kafka/data/zookeeper
```

## 起動


Zookeeperの起動

```
zookeeper-server-start ./kafka/config/zookeeper.properties
```

Brokerの起動

```
kafka-server-start ./kafka/config/server.properties
```

## Topicについて

### Topicの作成

```
kafka-topics --zookeeper 127.0.0.1:2181 --topic first_topic --create --partitions 3 --replication-factor 1
```

### 作成したTopicの確認

```
kafka-topics --zookeeper 127.0.0.1:2181 --topic first_topic --describe
```

### Topicの一覧

```
kafka-topics --zookeeper 127.0.0.1:2181 --list
```

### Topicの削除

```
kafka-topics --zookeeper 127.0.0.1:2181 --topic second_topic --delete
```

削除時はいろいろ問題でてくるみたいなので
このページが参考になるかも
[When/how does a topic “marked for deletion” get finally removed?](https://stackoverflow.com/questions/23976670/when-how-does-a-topic-marked-for-deletion-get-finally-removed)

## メモ

### 送信したメッセージの処理について

プロデューサーでメッセージを送信したとき
そのメッセージはラウンドロビンでパーティションに振り分けられる
そのため、コンシューマー側でメッセージを受信するときには
取得先のパーティションによってメッセージが決まるので
メッセージを送信した順番で処理するとは限らない

### 送信時につけるKeyについて

Keyをメッセージにつけると
その同じKeyがついているものは同じパーティションに保存されるようになる

### acks

データロストしたくない場合
十分なブローカーを用意して
レプリカ(ISR-in sync replica-)にもデータを同期するといい

プロデューサーの設定でacksはallにしておく

### Idempotent Producer

Kafka>=0.11のときに使用できる
ネットワークエラーなどでBrokerのackリクエストがプロデューサー側に届かなかった場合に
プロデューサー側から再度リクエストを送ることで重複してメッセージをコミットしなくなる

### 安全なプロデューサーの設定

Kafka < 0.11 のとき

- acks-all
- min.insync.replicas=2
- retries=MAX_INT
- max.in.flight.requests.per.connection=1

Kafka >= 0.11

- enable.idempotence=true
- min.insync.replicas=2
- aks-all,retries=MAX_INT,max.in.flight.requests.per.connection=5(default)

### Message Compression

compression.typeはdefaultがnone
gzip,lz4,snappyなどを選べる

### Delivery semantics for consumer

- At most once: メッセージロストの危険性あり
- At least once: メッセージが処理されてからコミットする。ただ、重複してデータを読み取る可能性があるのでメッセージの処理がidempotentであるようにする。

### Security

- Encryption
- Authentication
- Authorization

### Kafka with the Confluent CLI

Confluent Platform CLI
https://docs.confluent.io/confluent-cli/current/install.html


### Kafka development environment using Docker

https://github.com/conduktor/kafka-stack-docker-compose
初心者の僕がGO言語でpostgresSQLをいじるまで。

#PostgreSQLの準備


###Goの設定
#####PostgreSQL用のドライバをインストール

```
go get github.com/lib/pq
```

#####main.goの中身
```go:
package main

import (
	"database/sql"
	
	_ "github.com/lib/pq"
)
var Db *sql.DB
var err error

type Person struct {
	Name string
	Age int
}

func main()  {
	//sqlの作成
	Db, err = sql.Open("postgres", "user=test_user dbname=testdb password=8860 sslmode=disable")

	//エラーハンドリングとsqlとgoのリンク
	if err != nil {
		log.Panicln(err)
	}
	defer Db.Close() 
```


######ドライバとは..
同じ初学者の方は共感頂けるのではないでしょうか。ドライバって何？笑
G先生（Google）では、PCと外部機器をつなぐための橋渡しをするのがドライバーの役割です。とありましたがこの場合goとPostgreSQLを繋ぐために必要だと解釈しています。

####macの場合

######PostgreSQLのインストール

```
brew install postgresql
```

######文字コードをUTF-8でデータベースの初期化

```
initdb /usr/local/var/postgres -E utf8
```

######Postgresのバージョンチェック
```
postgres --version
```

#####新しいデータベースを作成する。　
この例ではtest_dbという名前です。

```
$ createdb test_db
```

#####作成したデータベースを開く

```
$　psql test_db
```

僕は最初にずっとここでつまずていて、、

```
$ psql

エラー文
psql: error: FATAL:  database "macの名前" does not exist
```
訳.
"macの名前"の名前のデータベースが存在しません。
作った覚えないんだけど、、、ってずっと思っていてなかなか前に進めませんでした。psqlだけで開こうとすると"macの名前"のデータベースを探そうとするのかな??
試しに"macの名前"で作成したらpsqlで入れたので多分そうです。

######ユーザー作成
データベースを開くことができたらこんな画面になると思います

```
psql (13.2)
Type "help" for help.

mydb2=#
```
おめでとうございます。あなたはデータベースの中にいます。
僕は初めで開けた時嬉しすぎで腕立てしました。(有り余る嬉しさを筋肉に置換する)

```
create user test_user password '8860';
```
test_userと'8860'は自由に設定出来ます。
user名とパスワードを作ります。
何かのアプリに登録するみたいにpsqlもこういうのが必要なんですね。

ここで一度情報を確認します。

```
\l
```
すると

```
                             List of databases
     Name      |  Owner   | Encoding | Collate | Ctype |   Access privileges   
---------------+----------+----------+---------+-------+-----------------------
 postgres      | mac名前　　　 | UTF8     | C       | C     | 
 testdb        | mac名前 　　　| UTF8     | C       | C     | 
 template0     | mac名前 　　　| UTF8     | C       | C     | =c/名前         +
               |          |          |         |       | 伏せます
 template1     | mac名前　　　 | UTF8     | C       | C     | =c/名前          +
               |          |          |         |       | 伏せます
(4 rows)
```

他にどんなものがあるにか確認できます。先程作成したものもありますね。
ちなみに

```
\q
```
でpsqlから抜けれます。

```
select usename from pg_user;
```
こちらはuserの確認になります。test_userが表示されたらおっけい。

#####作成したuserでデータベースを作る

```
create database testdb owner test_user;
```

これで\lnにて確認するとtest_userでデータベースが作られています。


https://qiita.com/nasuB7373/items/fecbc93c5484474c378f

https://qiita.com/hirocueki2/items/327dc6e87005edf622ad

https://qiita.com/Ksk8358/items/6629f597ddf7f4ccd97c

https://tektektech.com/psql-mac-environment/

https://www.postgresql.jp/document/8.2/html/tutorial-arch.html
CodePenで面白そうなテンプレート9+3選（JavaScriptアニメーション編）

#CodePenとは
CodePenはHTML、CSS、JavaScriptのコード入力画面と、その結果が表示される画面が一体で操作できるサービスです。
多くの作者がテンプレートを公開していて、自分の環境にフォークして使えます。

今回自分でWebアプリを作るにあたり参考にさせていただこうと調べた中で面白かったものを挙げていきます。

https://codepen.io/topics/


#お気に入りのanime.js
anime.jsは「A lightweight JavaScript animation library with a simple, yet powerful API」とのこと。
（いわゆる日本のアニメじゃなかった）

2021/5/21時点でFeaturedされた順に24個を調べてみました。（Like数順にソートする方法を知っている方、教えてくださいm(__)m）

個人的に直近で使えそう・使いたいものに◎をつけています。


https://codepen.io/codewitharyann/pen/poERmEM

↑　スマホの下部からテキストがぴょんと飛び出て、重力に引かれてちょっと落ちる感じが小気味よい。

https://codepen.io/lmgonzalves/pen/NyzKdr

↑　ビーカーの中の液体をマウスで引っ張れる。マウスを話すと本当の液体のように波打つ。面白い。使い道思いついてないけど好き。



https://codepen.io/andrewmillen/pen/MoKLob

**↑　◎　ボタンを押したら進捗バーに変化し、MAXになったら丸く変化してチェックマークのアクションになる。一気にお洒落サイトになりそう。**


https://codepen.io/limitedunlimited/pen/PQZNar

↑　木の葉を模したデザインがはらはらと落ちていく。まじか。こんなことまでできるのか。


https://codepen.io/akhil_001/pen/zXamxp

**↑　◎　文字や写真をカラーバーの動きで上に隠してまた表示させる。クール。**

https://codepen.io/borntofrappe/pen/JjoxRwG

**↑　◎　ナビゲーションバーを押すと項目が波打って左右から登場する。慣性の揺れも表現されていてポップな感じ。**

https://codepen.io/jsonhoward-the-typescripter/pen/pogZXNB

↑　ボタンが逃げる（笑）　ジョークサイトに良さそうだけどイラつかれそうw

https://codepen.io/borntofrappe/pen/aboPapm

↑　昼モードと夜モードを切り替える。トグルが太陽と月でおしゃれ。

https://codepen.io/retsugo/pen/XWmMKgW

↑　こちらもダークモードへの切り替えボタン。丸い太陽のボタンが三日月に変わる。シンプル。



追記）Gamesカテゴリを調べてたらこちらのカテゴリのほうがふさわしいものを見つけたのでこちらに追記します。

https://codepen.io/Manoz/pen/pydxK

↑　ローディング画面集。シンプルお洒落。

https://codepen.io/peanav/pen/ulkof

↑　マンスリーカレンダーでその日の予定を確認できる。前月や次月に移動も可能。

https://codepen.io/fluxus/pen/gPWvZm

***↑　◎　メニューボタンを押した際にメニュが縦タイルでパタパタと登場する。お洒落。***


#おわりに
しまった、延々と見てられるｗ　むしろ全部見尽くしたいｗ
でも時間は有限なのでこれくらいでやめておきます。

おすすめのテンプレートがありましたら是非コメントで教えてください。m(__)m
Laravelでvue3を使う手順

サクサク行きます。

### laravelのインストール

ララベルをイントールします。

```
composer create-project laravel/laravel --prefer-dist laravel8_vue3
```

### npm install

依存関係をインストールします。

```
npm install
```

### vue3をインストール

```
npm install vue@next --save-dev
```

### vue routerもインストール

```
npm install vue-router@4
```

`@4`をつけないと、'vue3'と互換性のないのがインストールされちゃう。

### webpackの編集

`vue()`をつけないとコンパイルでエラーが発生します。

```js:vue()を追加
mix.js('resources/js/app.js', 'public/js').vue() //ここ
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]);
```

###　コンポーネントを作成

`resources/js/components/ExampleComponent.vue`を作成します。

```vue:resources/js/components/ExampleComponent.vue
<template>
    <div>Hello Vue3</div>
</template>
```

中身はご自由に。

### route.jsの作成

ルーティングを記述するファイルを作る。（`resources/js/routes.js`）

```resources/js/routes.js
import ExampleComponent from './components/ExampleComponent.vue';
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
    {
        path: "/",
        component: ExampleComponent,
        name:'home',
    },
];

const router = createRouter({
    routes, // short for `routes: routes`
    history: createWebHistory(),
  })
  
export default router;

```

`vue-router`もバージョン4になって、記述方法が少し変わっているので注意。

### app.jsにを編集

```js:app.js
require('./bootstrap');
import router from "./routes";
import ExampleComponent from './components/ExampleComponent.vue';

import { createApp } from 'vue';

const app = createApp({
    components: {
        ExampleComponent
    }
}).mount('#app');
```

こんな感じにすればあと少し。

###  web.phpのルーティングを編集

```php:web.php
Route::get('/{any?}', function () {
    return view('welcome');
})->where('any', '^(?!api\/)[\/\w\.-]*');
```

### welcome.phpの編集

```
<head>
  <script src="{{ asset('js/app.js') }}" defer></script> //追加
<head>

<body>
  ~省略~
  <div id="app">
    <router-view></router-view>
  <div>
</body>
```

これで完成です。

おしまい！
【C→C++あるある】勝手にfopenとかfcloseとかしてくれるヘルパー


# 概要

　超絶、今更記事。でも意外と入門サイトとかには書いてない気がするｗ
　あると、ラムダ式とデストラクタの役割が理解出来て良い。

　現代プログラミングでは当たり前の内容なんですが、C++やっていると知らないと辛いときがたまにあります。
　C言語でしか書かれていないコード、C++使っている癖にこのような視点で変更されていないコードも世の中に結構出回っているので、たま～に役立ちます。

　std::ifstreamなんかはこの記事のような対策が打たれている（と思われる）ので、このようなことを考える必要はないです。

　**「はじめて学ぶC++11入門」**って感じです。

# コード

こんなコード数年ぶりに書きましたわ

```C++

#include <iostream>

int main()
{
	FILE* fpr;
	wchar_t buffer[256];

	if( _wfopen_s(&fpr, L"C:\\Test.txt", L"r") == 0) {
		fwscanf_s(fp, L"%s", &buffer, 256);
		wprintf(L"%s", buffer);
		fclose(fpr);
		return 0;
	} else {
		wprintf(L"Error");
		return 1;
	}
}

```

# 問題点

- fcloseは、_wfopen_sが成功したときしか実行されなくて、成功時と失敗時の表記揺れが怖い

- fcloseと書くことを忘れそう

# 解決方法

- fopen, fcloseの対になっている部分は、helperクラスを定義しておき、コンストラクタでfopen、デストラクタでfcloseを実行するように処理をする

- ラムダ式(std::function)を使って、具体的な処理は移譲する

```C++

#include <functional>
#include <iostream>

class IoHelper {
private:
	FILE* fpr;
	errno_t error;
public:
	IoHelper(std::wstring path, std::function<void(FILE*, errno_t)> func) {
		error = _wfopen_s(&fpr, path.c_str(), L"r");
		func(fpr, error);
	}
	virtual ~IoHelper() {
		if(error == 0 ) {
			fclose(fpr);
		}
	}
};

int main()
{
	{
		// このように定義すると、IoHelperが存在するローカルスコープの中では、
		// fcloseされないが、ローカルスコープから抜けると自動的にfcloseされる。
		IoHelper helper(L"C:\\Test.txt", [] (FILE* fp, errno_t error) {
			if( error == 0 ) {
				wchar_t buffer[256];
				fwscanf_s(fp, L"%s", &buffer, 256);
				wprintf(L"%s", buffer);
			} else {
				wprintf(L"Error");
			}
		});
	}

	{
		// ここではもうfcloseされている
	}

	return 0;
}

```

# 補足

　std::ifstream, std::mutexなどは、このような仕組みと似た設計となっています。
　わざわざ意図的にcloseやunlockをしなくても使えるのが良い。
kintoneのアクセス権を設定して表示する一覧を制限する

kintoneの一覧画面、
一覧で絞り込んでも、（すべて）を選択すると見られたくないレコードが見えてしまう！
だから、（すべて）を選べなくしたい！
のように、カスタマイズに頼って一覧表示に制限をかけたいと思ったことがある方はいませんか？

でもちょっと待って！カスタマイズの前に「アクセス権」見直してみましょう！
アクセス権の設定でやりたいことが実現できるかも！？

というわけで今回は表示制限に役立ちそうな権限の例を考えてみました。

# Aさんだけにはアプリの1レコードも見せたくないという場合
特定の人にはアプリ自体を見せないようにする設定です。

## アプリのアクセス権
アプリのアクセス権でAさんのチェックを全部外します。
Everyone（Aさん以外のみんな）は閲覧・追加・編集にチェックを入れておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/0ad66c03-d0b0-4299-887a-0bb298f63e14.png)

## 結果
Aさんでアプリにアクセスしようとすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/a2200378-125f-327f-4d3a-975f023947f7.png)

アプリ自体にアクセスできなくなります。

# Aさんはアプリにアクセスできていいけど、1レコードも見せたくないという場合

Aさんはアプリが見えてもいいけど、レコードは1つも見せたくないという場合は、アプリのアクセス権とレコードのアクセス権を設定します。

## アプリのアクセス権
アプリのアクセス権で閲覧権限にチェックを入れます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/e34b2987-cd28-cd5c-ddd1-57dee9bf1920.png)

## レコードのアクセス権
レコードのアクセス権でAさんを追加しどれにもチェックを入れない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/8a653f8d-c821-4be1-3810-27c6b8a57cf8.png)

## 結果
Aさんでアプリにアクセスすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/cb60a74c-b21b-a249-1e21-0b9bdf995dab.png)

データがありませんと表示されます。

# 自分の担当している顧客のレコードだけ閲覧・編集を許可する場合
Aさんに限らず、自分が担当しているレコードだけの閲覧・編集ができるようにしたい場合は、アプリにユーザー選択フィールドや組織選択フィールドを追加します。

## アプリの準備
ユーザー選択フィールドを設置しましょう。「社内担当者」のようなフィールド名にしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/54071b54-2c0e-210e-99e4-18e4e249e7dd.png)

適当に担当者を割り当てます。
一覧にも社内担当者フィールドを追加しておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/b2fb9143-4679-6393-4bf8-50b99c8389f5.png)


## アプリのアクセス権
Aさんのアクセス権は削除して、作成者とEveryoneのアクセス権だけにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/eaf3f7be-48d9-2dcf-1818-b3830ef02f21.png)

## レコードのアクセス権
レコードの条件と、フォームのフィールドを追加して、下図のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/e66977bf-b916-0d65-3c0e-c1339d9566b8.png)

## 結果
ログインユーザーが担当する顧客の一覧のみ表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/6ef51527-1bad-f251-dca8-dc1b403a82c6.png)

# レコードは全員が見れていいんだけど、顧客の担当者については社内の担当者だけが見れるようにしたい
アプリ、レコードのアクセス権に加えて、フィールドのアクセス権の設定もします。

## アプリのアクセス権
特に変更なしです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/de54d33b-a311-1ebe-01d0-ca89bd11b84d.png)

## レコードのアクセス権
Everyoneに閲覧権限を追加しておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/3f58799b-2e21-287d-e667-35ecfc69bbcc.png)

## フィールドのアクセス権
フィールドに担当者名と部署名を追加し、フォームのフィールドを追加ボタンでユーザーに社内担当者を追加、Everyoneの権限のチェックを外し、社内担当者の閲覧・編集権限にチェックを入れます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/854e9e06-3602-5e94-2259-d1a4a140ad9f.png)

## 結果
全レコードを閲覧することはできても、見てほしくないフィールドは（閲覧権限なし）になりました。
担当顧客のレコードは編集もできる状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/fae6040e-d288-81c8-0351-ee367c5e3815.png)


# まとめ
カスタマイズをする前に標準機能でできることもあるので、ぜひ色々と試してもらえたらなぁと思います。
文字コード（ASCIIコード）を取得するコードサンプル

# 動機

特定の文字を取り出したい時によくお世話になるコードをいちいち作るのが面倒くさいので備忘録

## 調査コード
``` 調査コード.java
class debug {
  public static void main(String[] args) {
    for (int i = 33; i < 128; i++) {
      System.out.println(i + ": " + (char) i);
    }
  }
}
```

## やりたかったこと
``` やりたかったこと.java
class debug {
  // 与えられた文字列を１文字ずつ調べて一致するものを取得したい場合、matchやsplitで正規表現をすると遅すぎたのでシンプルに文字だけ比較する。
  // 参考： https://blanktar.jp/blog/2015/11/java-character-count

  // mainから呼びたいのでstaticにしているが、実際に使う時は各クラスに合わせること
  final public static int searchChar(String str, char target) {
    int count = 0;

    for (char x : str.toCharArray()) {
      if (x == target) count++;
    }

    return count;
  }
}

```

# チートシート
参考ページ: http://www3.nit.ac.jp/~tamura/ex2/ascii.html
## 英字（大文字）
```
65: A
66: B
67: C
68: D
69: E
70: F
71: G
72: H
73: I
74: J
75: K
76: L
77: M
78: N
79: O
80: P
81: Q
82: R
83: S
84: T
85: U
86: V
87: W
88: X
89: Y
90: Z
```

## 英字（小文字）
```
97: a
98: b
99: c
100: d
101: e
102: f
103: g
104: h
105: i
106: j
107: k
108: l
109: m
110: n
111: o
112: p
113: q
114: r
115: s
116: t
117: u
118: v
119: w
120: x
121: y
122: z
```

## 数値
```
48: 0
49: 1
50: 2
51: 3
52: 4
53: 5
54: 6
55: 7
56: 8
57: 9
```

## 記号
```
33: !
34: "
35: #
36: $
37: %
38: &
39: '
40: (
41: )
42: *
43: +
44: ,
45: -
46: .
47: /
58: :
59: ;
60: <
61: =
62: >
63: ?
64: @
91: [
92: \
93: ]
94: ^
95: _
96: `
123: {
124: |
125: }
126: ~
```
Roomを暗号化してみる

#経緯
[こちら](https://at-virtual.net/securecoding/%E3%80%90%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%80%91owasp%E6%BA%96%E6%8B%A0%E3%81%AE%E8%84%86%E5%BC%B1%E3%81%A7%E3%81%AA%E3%81%84-ios-android%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B/)にもありますが、個人情報を扱う場合、データベースの暗号化の必要がありそうだったため、Roomは暗号化できるのか調べたところSQLCipherというライブラリが目に留まりました。

#参考
[AndroidにSQLCipherを導入してみる](https://qiita.com/ke__kyukyun1828/items/f7d655caff73853199bd)
[Protect your Room database with SQLCipher on Android | by ...](https://sonique6784.medium.com/protect-your-room-database-with-sqlcipher-on-android-78e0681be687)

#SQLCipherライブラリの導入
[こちら](https://www.zetetic.net/sqlcipher/sqlcipher-for-android/)を参考に、build.gradle(app)に追加します。

```gradle:build.gradle(app)

dependencies {

    implementation "net.zetetic:android-database-sqlcipher:4.4.2"
    implementation "androidx.sqlite:sqlite:2.1.0"
}

```

データベースを操作する部分

```kotlin:UserDatabase.kt

package com.example.sample

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SupportFactory

@Database(entities = [User::class], version = 2, exportSchema = false )
abstract class UserDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object{
        fun buildDatabase(context: Context): UserDatabase{
            return Room.databaseBuilder(
                    context,
                    UserDatabase::class.java,
                    "user.db"
            ).openHelperFactory(SupportFactory(SQLiteDatabase.getBytes("test123".toCharArray())))
                    .allowMainThreadQueries()
                    .build()
        }
    }
}


```

#ビルドした際のDBファイルの場所
[公式のドキュメント](https://developer.android.com/studio/debug/device-file-explorer?hl=ja)にある通り、
DeviceFileExplorerにて/data/data/app_name/databases内にDBファイルが作成される
(私の場合は/data/data/com.example.rworksample00026/databasesでした)
作成されたファイルをデスクトップとかにダウンロードします。


![WS001157.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/d602cac9-a76c-dd23-f32e-f2543a58fd3e.jpeg)

![WS001156.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/7079ad90-250f-6449-0b20-06e7890eeccf.jpeg)

![WS001158.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/1bb6359a-ea77-cd56-671b-6fbe051da3d2.jpeg)

![WS001159.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/2d78f0b2-e6c4-5318-7bf6-8fdad76bec6a.jpeg)


#データベースの中身の確認
[DB Browser for sqlite](https://sqlitebrowser.org/dl/)にてSQLcipher側を起動してパスワードを入力すると中身が確認できます。(中身のデータがあればデータも確認できます。)

![WS001160.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/8179c4e6-22df-5c2f-ada3-4ff7f4154585.jpeg)

![WS001161.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/0fa40329-cb4e-3b97-ef2a-4a35675a7c7e.jpeg)

![WS001162.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/a549d689-98eb-967f-7abd-3d3548df540a.jpeg)