type holyshared = Engineer<mixed>

技術的なことなど色々

画像の変換にimgixを使う

アップロードした画像を変換するのに最近は、SaaSを使用しています。
普段はcloudinaryを使用していたのですが、imgixを試してみたら導入が簡単でした。

設定そのものは、imgixの設定画面でAWSのS3のバケットを指定して、アクセスキー、シークレットキーを指定する感じです。
後はimgixのライブラリを利用して、URLのパスを生成だけです。

import ImgixClient from 'imgix-core-js';

const client = new ImgixClient({
  domain: "example.imgix.net",
});

const key = "/wxyz_user_assets/original.jpg"; // S3のオブジェクトキー
const url = client.buildURL(key, { w: 400, h: 300 });

console.log("resize image: %s", url);

URLのセキュリティトークン

管理画面で Secure URLs を有効にすると、URLにセキュリティ用のトークンを追加できます。
クライアントのオプションで、トークンを設定するとURL生成時に、トークン + パス + クエリパラメータ をMD5でハッシュ化したパラメータをURLのクエリに追加してくれます。

このトークンがない状態で画像のURLにアクセスすると、403 のステータスコードを返してくれます。
この仕組みは特定のユーザーにしか閲覧を許可したくない場合に有効です。

URLが外部に漏れてしまっても、トークンを変えるだけで、アクセスできなくすることができます。
また、第三者にアプリケーションで使用していないサイズの画像をURLを変えることで生成されることも防げます。

可能な限り有効にしておいた方がいいと思います。

import ImgixClient from 'imgix-core-js';

const client = new ImgixClient({
  domain: "example.imgix.net",
  secureURLToken: process.env.IMGIX_SECURE_TOKEN
});

const key = "/wxyz_user_assets/original.jpg"; // S3のオブジェクトキー
const url = client.buildURL(key, { w: 400, h: 300 });

console.log("resize image: %s", url);

画像の読み込み

クライアントで画像のexifを読み出すのに普段はblueimp-load-imageを使用しています。
画像をアップロードをするフォームの投稿日のデフォルト値を指定したりする時や、写真をトリミングするコンポーネントなどで利用しています。  

自分がメンテナンスしているアプリケーションで古いバージョン2系を使用していたので、新しいバージョンの5系にアップグレードしました。

const options = {
  maxWidth: 600,
  orientation: true,
  meta: true,
  canvas: true // 1. オプションの追加
};
const photoEnableOrientation = document.getElementById('photoEnableOrientation');
photoEnableOrientation.addEventListener('change', (evt) => {
  loadImage(evt.target.files[0], options).then((result, meta) => {

    const exif = result.exif.get("Exif"); // 2. Exif IFDの読み出し
    const dateTimeOriginal = exif.get("DateTimeOriginal");
    console.log(dateTimeOriginal);

    document.body.appendChild(result.image);
  }).catch(err => {
    console.log(err);
  });
});

主な変更点は下記の通りでした。

  1. オプションの修正

    古いバージョンだと、コールバック関数の引数がHTMLCanvasElementだったのが、オプションでcanvas: trueを指定しないと、デフォルトではHTMLImageElementになる。

    また、Promiseだと結果オブジェクトになっていて、imageプロパティで参照できるようだ。

  2. exifオブジェクトのインターフェース変更の修正

    読み込み結果のオブジェクトにexifオブジェクトがあったが、参照キーが変わったようで撮影日時が取れなくなっていた。
    DateTimeOriginal指定で参照できていたが、Exifキーを指定して、Exif IFD の結果に対して、DateTimeOriginalを指定する必要があった。

修正自体はそんなに大したことなかったです。

追加で物をサマリーポケットに預けた

あんまり着ていない物とか、使ってない物をまたサマリーポケットに預けてみました。
今回もアパレルのボックスを利用してしました。

物としては下記の物をパッケージングして送りました。

  • LEVI'SのGジャン
  • LEVI'Sのギャリソンベルト x 3
  • ナイロン製のアウター
  • サイドゴアブーツ
  • 6インチブーツ
  • コットンのハット
  • ベロアのナップザック

箱の高さはGジャン、ナイロン製のアウターでちょうどいい感じになりました。
頑張れば、軽めのアウター3着くらいは行けそうです。

サマリーポケット使ってみた

2ヶ月前くらいに、靴の整理にサマリーポケット使ってみました。
箱はアパレルの横長を頼んでみました。

f:id:holyshared:20200408190211j:plain
梱包している箱

送った内容は、最終的に次のようになりました。

  • 短靴4足
  • 羽織りようのアウター 1着
  • パーカー 1着

サイズ間としては短靴が最大で6足位入りそうな感じでした。

使用感

梱包してからアプリで集荷するのが楽でしたね。
伝票のこととか考えなくて済むので...。

とりあえず捨てようか迷っているなら、サマリーポケットで預けてしまうのがいいのかもしれません。
服とかも沢山あるので、重宝しそうです。

久しぶりにスニーカー買った

ここ数年は革靴(ドレスシューズ / ワークブーツ)ばかり履いていてスニーカーを履いてなかった。
それはそれでいいとして、あまりにも履いていないのでスニーカーの履き心地どん感じか忘れてしまっていた。

なのでスニーカーを買うことにしたわけです。

BROTHER BRIDGE model: BERLIN
BERLIN

はい、名作のジャーマントレーナーです。
いくつかのブランドがサンプリングしていますが、私は魂を売っているBROTHER BRIDGEさんのBERLINを買いました。
ソール交換ができるのやっぱりいいですね、長く履きたいし。

BROTHER BRIDGEさんはずっと気に入っているブランドです。
いつも浅草のFLAGSHIP STOREで買っています。

履いた感想としては、軽くて屈曲性がメチャクチャいいですね。
グリップ力もあるので、グイグイ足が進みます。

ブーツはガツガツ歩く感じですが、また変わった履き心地です。

プリズナージャケットとパンツの経年変化

何回かWAREHOUSEから出ているプリズナージャケット & プリズナーパンツ。
これはJUNKY STYLE別注モデルで、ボーダーの染める回数を増やして黒になるようになっているやつです。

1年ちょっとの着用で、経年変化の記録ために写真を撮っていたのを忘れてた。

プリズナージャケット

パンツより先に買ったはず。
多少色が薄くなっているような気がする。

プリズナージャケット(WAREHOUSE / JUNKY STYLE別注)
プリズナージャケット

f:id:holyshared:20200402163240j:plain
色落ちの具合

プリズナーパンツ

普段30inchのパンツを着用することが多いのだけど、これは31inch。
ノンウォッシュのものを買ったので、洗ったらメチャクチャ縮んだのを覚えている。

これもちょっと色が落ちた気がする。

プリズナーパンツ (WAREHOUSE / JUNKY STYLE)
プリズナーパンツ

パンツの色落ち具合

上下セットで着ることはあまりないけど、囚人服はいいですね。
ボーダー柄じゃないやつもあるし。

Mongooseドキュメントのserialize/deserialize

Mongooseを利用している場合、読み込んだドキュメントをserialize/deserializeしたい場合があると思います。
serializeは雑にtoJSON、でいいと思うのですがdeserializeはどうするのか?

一つのやり方として、hydrateメソッドを使用するやり方があります。

const user = await User({
  name: 'name',
  password: 'password',
  createdAt: new Date(),
  updatedAt: new Date()
});
await user.save();

// JSONからUserドキュメントに戻す
const jsonOfUser = await user.toJSON();
const hydratedUser = User.hydrate(jsonOfUser);

console.log(hydratedUser); // User { name: ... }

ここで気になるのがhydrateした場合は、永続化済みかどうかのコンテキストがわかるのかという疑問があります。
通常はドキュメントのisNewプロパティ調べられますが、hydrateした場合はどうかということです。

検証してみる

試してみた結果はisNewプロパティがfalseになっていることがわかりました。

const user = await User({
  name: 'name',
  password: 'password',
  createdAt: new Date(),
  updatedAt: new Date()
});
console.log(user.isNew); // まだ永続化していないので、true
await user.save();
console.log(user.isNew); // 永続化したのでfalse

const jsonOfUser = await user.toJSON();
const hydratedUser = User.hydrate(jsonOfUser);

console.log(hydratedUser.isNew); // falseが返ってくるが根拠はなんだろうか?

しかし、isNewがfalseになる根拠はなんでしょうか?
これはMongooseのソースを読まないとわからないのでみてみます。(バージョンはv5.8.9)

// model.js
Model.hydrate = function(obj) {
  _checkContext(this, 'hydrate');

  const model = require('./queryhelpers').createModel(this, obj);
  model.init(obj);
  return model;
};

queryhelpersを読み込み、createModelを実行してモデルを初期化しているようです。
引数にisNewオプションを指定していますね。
なので、hydrateでドキュメントに戻した場合は強制的にisNewがfalseになります。

// queryhelpers.js
return new model(undefined, fields, {
  skipId: true,
  isNew: false,
  willInit: true
});

当たり前といえば当たり前ですね。
てっきりhydrateを使用する際の引数のオブジェクトにObjectIdが指定されていれば、永続化済みの状態にするのかと思っていたのですが そんなことはなかったです。