UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

2022年振り返り

2022年の振り返り。

とりあえず今年も1月5日の仕事始めから12月23日の仕事納めまでの月〜金の毎日というルールでブログを書くことができました。

丸3年間続けられました。

ネタは毎日ありましたが、書く時間がなく大晦日まで忙しい毎日でした。

最近は昔の記事に助けられることが多く、やっていてよかったと思うことが多いです。

その他のアウトプット

今年は公の場で登壇を1つやりました。

uga-box.hatenablog.com

現場でやったことをDDDと絡めて話しましたが、約200人ほどが聞いてくれたようで、苦労が昇華された気分です。

スライドが初めてホッテントリに載り、思い出に残る発表でした。

来年に向けて

毎日ブログはやり遂げた感もありますが、なんとなく駄文が多くなってきていて質がよくないと感じています。

来年はプライベートで変化があるため、量より質を重視したアウトプットができたらと考えています。

1年間お疲れ様でした。良いお年を。

【Elasticsearch】ProfileAPIの結果をKibanaで可視化する

Elasticsearchの検索で異様に遅いクエリがあった

このクエリのProfileがみたいと思っていたところ、以下の記事をみつけたのでKibanaで可視化してみる

dev.classmethod.jp

localhost:9200でアクセスできるESにdocker上のKibanaからアクセスする方法は以前に記事にしたので、これの方法を使う

uga-box.hatenablog.com

このときから、ESのホスト名を指定する環境変数ELASTICSEARCH_URLELASTICSEARCH_HOSTSになっていたので、ちょっと修正

立ち上げたKibanaをみたところ

Dev Tools > Search Profiler でクエリを入力して実行すると、下図のようにプロファイルが表示された

1つの緯度経度に対して2km圏内のデータを取得するという条件が重いことがわかった

【Elasticsearch】null_valueの設定をしていないフィールドがnullのものを検索したい

Elasticsearchのあるフィールドがnullであるものを検索したい

つらいのが、そのフィールドにnull_valueを設定していないため、"NULL"という文字列で検索ができない

null_value | Elasticsearch Guide [8.5] | Elastic

そこで思いついたのが、existsを使う方法

以前、existsを使ってElasticsearchで不要なフィールドの削除しようとしたが、existsクエリはフィールドがnullかつnull_valueの設定をしていない場合、フィールドを持っていないと判断するため、ちょっと工夫が必要という記事を書いた

qiita.com

逆にいうと、nullのフィールドは存在していないという判定になるので、must_notと組み合わせてあげれば検索できそう

【Next.js】Next.jsでテストを行う

フレームワークをNext.jsへ移行することを考えている

このとき、Jestで書いたテストもNext.jsに移行することを考えているが、ドキュメントを読むとNext.js 12 のリリース以降、Next.jsにはnext/jestという組み込みモジュールが用意されていることを知った

また、テストの実行にRustコンパイラを用いるか、従来のBabelを用いるかの選択もできるみたい

nextjs.org

せっかくなので、Rustコンパイラを使うようにしてみる

ドキュメントにあるように、以下のような jest.config.js を用意する

const nextJest = require('next/jest')

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: 'jest-environment-jsdom',
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

これに加えて、今使っているJestの設定ではモジュールのパスにエイリアスを設定して絶対パスでimportしているため、これを設定したい

jestに渡す設定は上記のcustomJestConfig内に記載する

なので、以下のように記載するとモジュールのパスにエイリアスを設定することができる

const customJestConfig = {
  // Add more setup options before each test is run
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ["node_modules", "<rootDir>/"],
  testEnvironment: "jest-environment-jsdom",
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
};

これで簡単にテストの移行ができた

【GAS】Google Driveにファイルを保存するときフォルダあるなしを判別したい

Google Apps ScriptでGoogle Driveにファイルを保存するとき、フォルダがあるなしで判別して

なければ作成し、あればそのフォルダを使いたい

こちらを参考にさせていただいた chan-lettuce.hatenablog.com

まず、検索を開始するフォルダがある場合はそのフォルダIDを指定して、以下のようにフォルダインスタンスを作っておく

var drive = DriveApp.getFolderById("id")

なければドライブのルートを指定する

var drive = DriveApp.getRootFolder()

次にそのフォルダ配下に対して、フォルダ名で検索する

var folderList = drive.getFoldersByName(folderName);

同名のフォルダのリストが返ってくるのでループして存在を確認する

存在すればそれをファイルを保存するフォルダとして、存在しなければ以下で作成して保存先にする

var folder = createFolder(name)

使用したAPIは以下に仕様が書かれている

developers.google.com

【Web セキュリティ】セキュリティを強化するHTTPヘッダーたち

Webセキュリティを強化するためのHTTPヘッダーについて調べた

referrer policy

httpヘッダーでリファラー情報をリクエストにどれだけ含めるかを制御する

Referrer-Policy - HTTP | MDN

サイトによってはリファラによって流入元の情報が必要以上に遷移先に知られることになるため、このヘッダーを設定しこれを防ぐ

web.dev

構文は以下

Referrer-Policy: no-referrer
Referrer-Policy: no-referrer-when-downgrade
Referrer-Policy: origin
Referrer-Policy: origin-when-cross-origin
Referrer-Policy: same-origin
Referrer-Policy: strict-origin
Referrer-Policy: strict-origin-when-cross-origin
Referrer-Policy: unsafe-url

ブラウザによってデフォルト値が違うので、行いたい制御に合わせて設定しておくのがよさそう

cross origin embedder policy(COEP)

サイトで読み込んでいる外部ソースを、信頼する外部ソースからのコンテンツのみを許可するようにすることができる

developer.mozilla.org

構文は以下

Cross-Origin-Embedder-Policy: require-corp

これを付与した場合は、同じオリジンからリソースをロードするか、別のオリジンからロード可能として明示的にマークされたリソースのみをロードすることができる

また、SharedArrayBufferなどのAPIの利用する際には必須の設定である

uga-box.hatenablog.com

permissions policy

ブラウザのカメラや位置情報の取得といったブラウザ機能の使用を制御する

developer.mozilla.org

構文は以下

Permissions-Policy: <directive> <allowlist>

例えば次の指定をした場合、位置情報は同一オリジンとfoo.comからのドキュメントのみで使用でき、カメラへのアクセスはこのドキュメントを含むすべてのドキュメントで無効になっており、全画面表示モードは任意のフレームに許可される

Permissions-Policy:
    geolocation=(self "https://foo.com"),
    camera=(),
    fullscreen=*

webappsec-permissions-policy/permissions-policy-explainer.md at main · w3c/webappsec-permissions-policy · GitHubより

content security policy(CSP)

XSSやデータインジェクション攻撃のような、特定の種類の攻撃を検知し、影響を軽減・報告するための設定

developer.mozilla.org

構文は以下

Content-Security-Policy: policy

例えば、サイト管理者が、信頼されたドメインとそのすべてのサブドメインからのコンテンツを許可したい場合は以下のようにする

Content-Security-Policy: default-src 'self' example.com *.example.com

【JavaScript】fetchAPIのres.json()でエラーが発生する

fetchAPIを使っていて、以下のエラーが出た

TypeError: body used already for: https://my-app/remove-favorite

調べたところ、レスポンスを.json()JSONオブジェクトとして解析していたが、Content-Typejsonでないことが原因だった

github.com

レスポンスがJSONオブジェクトでない場合は、.text()にして変換したところエラーが解消された

今後はレスポンスのContent-Typeを確認すべきだが、確証がない場合は以下のようにしておくのが良いかも

res = await fetch(url)
if (res.headers.get('content-type').includes('json')) {
  await res.json()
} else {
  await res.text()
}