20210304のNode.jsに関する記事は7件です。

LINE × スプレッドシートのススメ(家計簿アプリ編)

はじめに

最近ですが、LINEでやり取りするトランザクションデータをデータベースではなく、ユーザのスプレッドシートで直接管理することを推してます

メリットとしてはこんな感じです。
1. サービス内でデータを保持しないということは、データ保持の責任リスクを軽減できる
2. ユーザに馴染みのあるExcel形式であり、スプレッドシートの機能で可視化できるのでデータ分析もしやすい

このメリットを活かすサービスとして、家計簿アプリを作りました。

家計簿アプリについて

コンセプト

  1. 後で家計簿に書こうとしても、ついつい忘れてしまう。なので、モバイルアプリで買ったその場で登録!!
  2. それでも人間は忘れてしまう。そんな時は、モバイルアプリでちまちま登録せず、PCでまとめて登録しよう!!
  3. 使い慣れたExcel形式だから、自分の好きなようにデータ分析もできる!!

2020_AdventCalendar用画像.png

2020_AdventCalendar用画像 (1).png

一応、以前作ったプレゼンですが、こちらでもコンセプトを語ってます。

(参考)LIFF×スプレッドシートのススメ

今回の改良点

今までは単純に買ったものをスプレッドシートに書き込むだけでした。
qiita①.png

巷でこんな声を聞きました。
「買い物してたら、ついつい買いすぎちゃって、月の生活費の予算を超えちゃったわ」

・・・なるほど。
じゃあ、月のトータル金額が見れるようにしましょう。

サーバー環境

サーバサイドはNode.jsで作っており、スプレッドシートの操作は以下のモジュールを使ってます。
google-spreadsheet

※Githubにも全ソースを上げてますが、以下のソースコードはQiitaに載せるにあたって分かりやすくするためにGithubのソースコードとは多少異なります。

家計botアプリ

まずは月の集計金額を出す必要があるので、query関数をスプレッドシートに書き込んでます。

let doc = new GoogleSpreadsheet(sheetId);// スプレッドシートIDを指定

// 認証関連のコードは省略します(※そのうちQiitaに記事書きます)
// スプレッドシート(doc)から、対象のシート(sheet)を取得したところから書きます。

// query関数を書き込むセルの指定
await sheet.loadCells('F1');
let cell = sheet.getCellByA1('F1');
// 関数を書き込む際は「formula」を使います。
cell.formula =  `=query(A:C,"select B,sum(C) where B is not null group by B label B 'sum 買ったもの'",1)`;
sheet.saveUpdatedCells();

これで、query関数を用いて集計できました。
qiita②.png

これだけではツマラナイので、グラフも入れて分かりやすくしてみます。
とはいえ、スプレッドシートの関数でグラフ化ができるのは今のところ少ないようです。

※本当は円グラフを使いたかったのですが、関数ではできないようなので棒グラフにしてみました。

// for文でぐるぐる回して、SPARKLINE関数を使って棒グラフを追加してます
for (let i = 2; i <= index; i++) {
  await sheet.loadCells(`H${i}`);
  let cell = sheet.getCellByA1(`H${i}`);
  cell.formula =  `=SPARKLINE(G${i}, {"charttype","bar";"max",MAX(G:G)})`;
  sheet.saveUpdatedCells();
}
sheet.saveUpdatedCells();

すると、こんな感じでグラフが出ました
qiita③.png

モバイルでも確認できますので、買い物中も見れます

今後の改良点

実は、買ったもの(商品名)で集計しているので、例えば「生活費」みたいなカテゴリで集計する形にはまだなってません。
今度はその辺りを直したいと思ってます。

最後に

今回は改良した部分を中心に記事を書きましたが、そもそもユーザのスプレッドシートに書き込むための設定方法については書いてません。
次回はその辺りを書きたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Discord.js 技術メモ #1

アクティビティを設定する

Botのアクティビティを設定する

client.on("ready", () =>
{
  client.user.setActivity("Yuki | https://discord.gg/CN4dYAVYXW", {type: 'PLAYING'});
});

ファイル読み込み

ファイルを読み込み配列に入れる

const fs = require('fs');
const readline = require("readline");
let readdata = [""];

var stream = fs.createReadStream("yuki/read.txt", "utf8");
var reader = readline.createInterface({ input: stream });
reader.on("line", (data) => {
 readdata.push(data)
})

ファイル書き込み

ファイルを書き込む配列に入れる

const fs = require('fs');

fs.writeFileSync('yuki/write.txt', 'data');

特定のチャンネルにメッセージを送信する

特定のチャンネルにメッセージを送信する

client.channels.cache.get('000000000000000000').send({embed: {
    author: {
      name: "結貴 - Yuki による宣伝",
      icon_url: client.user.avatarURL()
    },
    description: "公式サーバーの招待URL \n https://discord.gg/CN4dYAVYXW",
    color: 10181046,
    timestamp: new Date(),
    footer: {
      icon_url: client.user.avatarURL(),
      text: "結貴 - Yuki"
    }
  }}
)

メンバーにロールを付与する (Role Name)

メッセージの送信者にロールを付与する

 const member = message.guild.members.cache.find((member) => member.id === message.author.id)
 member.roles.add(role)

メンバーからロールを剥奪する (Role ID)

メッセージ送信者からロールを剥奪する

 const role = message.guild.roles.cache.find((role) => role.id === "000000000000000000")
 const member = message.guild.members.cache.find((member) => member.id === message.author.id)
 member.roles.remove(role)

わからないことがあったら

ここで質問を受け付けております

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【vue.js】フロントエンド開発にDockerを使う。つらみもあるよ

今までローカルでフロントエンド開発やってたけど、Docker使ってみるか〜。

でもDocker難しくてわからないよ〜〜〜〜〜〜〜!!!!!!

対象読者はvue-cli開発経験のある方です。
最終的なコードはこちら(github)

なぜDockerを使うのか

バックエンドの人たちがDocker信者すぎてフロントエンド開発者の肩身が狭いから

「環境を揃えたいよね」というフワッとした動機から。
実際使ってみて今のところメリットは感じていないですが、vue-cliのバージョンを固定したりするのは追々嬉しいことになるのかも、と思います。未来への投資だと思ってやっていきます。

「Dockerでフロントエンド開発する」とは

どこまでDockerに担わせるか、というところですが、調べた感じだと「実行環境」をDockerで構築するのがベターみたいですね。
image.png
クジラの写真なかった。

vueをDockerで動かすまでの手順

vue-cliプロジェクトの作成

ローカル環境でプロジェクトを作成します。
ローカルの環境はこんな感じです。新規プロジェクトを始めるときに慌てて最新バージョンにしたりしてます。

$ node --version
v14.15.5
$ npm --version
6.14.11 
$ vue --version
@vue/cli 4.5.11

あとはいつも通り

$ vue create docker-vue
$ ls -a
.                 .gitignore        node_modules      public
..                README.md         package-lock.json src
.git              babel.config.js   package.json

Dokcerfileを作成

プロジェクトディレクトリに作ります

Dockerfile
FROM node:14.15.5-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN apk update \
    && npm install -g npm@6.14.11 @vue/cli@4.5.11 \
    && npm install

FROMでnodeイメージのバージョンを指定します。今回はローカルと同じにします。
WORKDIRはDockerコンテナ内でのプロジェクトディレクトリです。なんでもよいです。
COPYでpackage.jsonとpackage-lock.jsonをWORKDIRにコピーします。
RUNでパッケージ等インストールし、環境構築します。ここでnpmとvue-cliのバージョンを固定します。

docker-compose.ymlの作成

docker-compose.yml
version: '3'
services:
  app:
    container_name: docker-vue-test
    build: .
    ports:
      - 8080:8080
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    stdin_open: true
    tty: true
    command: "npm run serve"

コンテナ名をdocker-vue-testと敢えてつけてます。あとで使います。
最後npm run serveを命令することで、コンテナ起動と同時にvueサーバーを起動させます。

動作確認する

まずDockerイメージをビルドします。

$ docker-compose build

そしてコンテナを起動。

$ docker-compose up -d

http://localhost:8080/ にアクセスすると、いつものVueの画面が表示されます。

(任意)ポートを変える

ここまででDockerで実行環境を作るという目標は達成しましたが、
8080ポートは何かと他のプロジェクトでも使ったりするので、明示的にポートを指定してみます。

vue.config.jsの作成

まず、vue-cliをどのポートを使って起動させるか指定します。
プロジェクトディレクトリにvue.config.jsを作成します。

vue.config.js
module.exports = {
  devServer: {
    port: 9000,     # 好きな数字にする
    host: '0.0.0.0',
    disableHostCheck: true,
  },
};

docker-compose.ymlの編集

docker-compose.ymlのportsを、先ほど指定した数字に合わせて修正します。

    ports:
      - 9000:9000

Docker再起動

$ docker-compose stop
$ docker-compose up -d

今度は http://localhost:9000/ で画面が確認できます。

所感

良いところ

いつも通りローカル開発でき、ホットリロードで画面に反映されます。普通です。

うーん…ってなったところ

ホットリロードしなくなった時、ローカル実行ならブラウザのキャッシュをクリアすれば大体治っていたのですが、
Dockerで実行してるとキャッシュを消しても治らないことがあります。
そういう時はコンテナをstop/startするか、全然関係ない箇所をいじったりすると治りました。ここはちょっとよくわからない…

また、追加でライブラリなどを入れる時はコンテナ側にインストールする必要があります。

$ docker exec -it docker-vue-test sh
/usr/src/app # npm install hogehoge...

さいごに

開発初期段階ではまだ「Dockerにして幸せだなあ」と思うことはないです。

ただ手順もそんなに多くはないですし、git cloneしたあとに
以前はnodebrewでnodeバージョンを変えてnpm installしてrun serveして…という手数を踏んでいたところが
Dockerのイメージビルド、コンテナ起動だけになるのは少しシンプルになって小気味好いかもしれませんね。知らんけど。

Dockerに頼りきりにならず、中で何をしているか理解することもとても重要だと思っているので、
それを心に留めつつ今後もDockerと仲良くしていきたいと思います。

参考記事

ローカルを汚さずdockerを使ってvue.jsの開発環境を作る[vuecli4]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】フロントエンド開発にDockerを使う。つらみもあるよ

今までローカルでフロントエンド開発やってたけど、Docker使ってみるか〜。

でもDocker難しくてわからないよ〜〜〜〜〜〜〜!!!!!!

対象読者はvue-cli開発経験のある方です。
最終的なコードはこちら(github)

なぜDockerを使うのか

バックエンドの人たちがDocker信者すぎてフロントエンド開発者の肩身が狭いから

「環境を揃えたいよね」というフワッとした動機から。
実際使ってみて今のところメリットは感じていないですが、vue-cliのバージョンを固定したりするのは追々嬉しいことになるのかも、と思います。未来への投資だと思ってやっていきます。

「Dockerでフロントエンド開発する」とは

どこまでDockerに担わせるか、というところですが、調べた感じだと「実行環境」をDockerで構築するのがベターみたいですね。
image.png
クジラの写真なかった。

vueをDockerで動かすまでの手順

vue-cliプロジェクトの作成

ローカル環境でプロジェクトを作成します。
ローカルの環境はこんな感じです。新規プロジェクトを始めるときに慌てて最新バージョンにしたりしてます。

$ node --version
v14.15.5
$ npm --version
6.14.11 
$ vue --version
@vue/cli 4.5.11

あとはいつも通り

$ vue create docker-vue
$ ls -a
.                 .gitignore        node_modules      public
..                README.md         package-lock.json src
.git              babel.config.js   package.json

Dokcerfileを作成

プロジェクトディレクトリに作ります

Dockerfile
FROM node:14.15.5-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN apk update \
    && npm install -g npm@6.14.11 @vue/cli@4.5.11 \
    && npm install

FROMでnodeイメージのバージョンを指定します。今回はローカルと同じにします。
WORKDIRはDockerコンテナ内でのプロジェクトディレクトリです。なんでもよいです。
COPYでpackage.jsonとpackage-lock.jsonをWORKDIRにコピーします。
RUNでパッケージ等インストールし、環境構築します。ここでnpmとvue-cliのバージョンを固定します。

docker-compose.ymlの作成

docker-compose.yml
version: '3'
services:
  app:
    container_name: docker-vue-test
    build: .
    ports:
      - 8080:8080
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    stdin_open: true
    tty: true
    command: "npm run serve"

コンテナ名をdocker-vue-testと敢えてつけてます。あとで使います。
最後npm run serveを命令することで、コンテナ起動と同時にvueサーバーを起動させます。

動作確認する

まずDockerイメージをビルドします。

$ docker-compose build

そしてコンテナを起動。

$ docker-compose up -d

http://localhost:8080/ にアクセスすると、いつものVueの画面が表示されます。

(任意)ポートを変える

ここまででDockerで実行環境を作るという目標は達成しましたが、
8080ポートは何かと他のプロジェクトでも使ったりするので、明示的にポートを指定してみます。

vue.config.jsの作成

まず、vue-cliをどのポートを使って起動させるか指定します。
プロジェクトディレクトリにvue.config.jsを作成します。

vue.config.js
module.exports = {
  devServer: {
    port: 9000,     # 好きな数字にする
    host: '0.0.0.0',
    disableHostCheck: true,
  },
};

docker-compose.ymlの編集

docker-compose.ymlのportsを、先ほど指定した数字に合わせて修正します。

    ports:
      - 9000:9000

Docker再起動

$ docker-compose stop
$ docker-compose up -d

今度は http://localhost:9000/ で画面が確認できます。

所感

良いところ

いつも通りローカル開発でき、ホットリロードで画面に反映されます。普通です。

うーん…ってなったところ

ホットリロードしなくなった時、ローカル実行ならブラウザのキャッシュをクリアすれば大体治っていたのですが、
Dockerで実行してるとキャッシュを消しても治らないことがあります。
そういう時はコンテナをstop/startするか、全然関係ない箇所をいじったりすると治りました。ここはちょっとよくわからない…

また、追加でライブラリなどを入れる時はコンテナ側にインストールする必要があります。

$ docker exec -it docker-vue-test sh
/usr/src/app # npm install hogehoge...

さいごに

開発初期段階ではまだ「Dockerにして幸せだなあ」と思うことはないです。

ただ手順もそんなに多くはないですし、git cloneしたあとに
以前はnodebrewでnodeバージョンを変えてnpm installしてrun serveして…という手数を踏んでいたところが
Dockerのイメージビルド、コンテナ起動だけになるのは少しシンプルになって小気味好いかもしれませんね。知らんけど。

Dockerに頼りきりにならず、中で何をしているか理解することもとても重要だと思っているので、
それを心に留めつつ今後もDockerと仲良くしていきたいと思います。

参考記事

ローカルを汚さずdockerを使ってvue.jsの開発環境を作る[vuecli4]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodeのバージョンを切り替える(複数管理する)

始める前に

※対象はMacになります
流れとしては
・nodebrewのインストール
・環境変数の設定
・nodeのインストール
・nodeのバージョンの切り替え
となります。

nodebrewのインストール

brew install nodebrew
nodebrew -v

バージョンが表示されればOK

環境変数の設定

で設定ファイルを開きます

vi ~/.bash_profile

以下の1行を追加

export PATH=$HOME/.nodebrew/current/bin:$PATH

設定を反映させる

source ~/.bash_profile

セットアップ

nodebrew setup

nodeのインストール

インストール可能なバージョンを確認

nodebrew ls-remote
nodebrew install-binary <version>

nodebrew installでもインストールできますが、上記のコマンドの方が早い

複数のバージョンをインストールできます

ちなみに<version>には以下の表記が使用できる
・v12.16.3
・12.16.3
・stable (安定版)
・latest (最新版)

以下のコマンドで現在インストールしているnodeのバージョンを確認できる

nodebrew list

nodeのバージョンの切り替え

nodebrew use <version>
node -v

バージョンが表示されればOK

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【書きかけ】Lambdaからクリアテキスト署名メールを送ろうとして2ヶ月かかった話

SMIMEでメールを暗号化して送信していましたが、SMIMEに対応していないメーラーだと本文が読めないので クリアテキスト署名 という形式で送ることになりました。
やり方が全然わからずプレッシャーに押しつぶされそうになりながらもなんとか解決できた経緯を書いていきます。

環境

  • Lambda(Node.js 12.x)
  • SES
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaからクリアテキスト署名メールを送ろうとして2ヶ月かかった話

SMIMEでメールを暗号化して送信していましたが、SMIMEに対応していないメーラーだと本文が読めないので クリアテキスト署名 という形式で送ることになりました。
やり方が全然わからずプレッシャーに押しつぶされそうになりながらもなんとか解決できた経緯を書いていきます。

環境

  • Lambda(Node.js 12.x)
  • SES

クリアテキスト署名とは

PKI関連技術に関するコンテンツ

SMIMEでメールを暗号化して送信すると、SMIMEに対応していないメーラーだとメールの本文が読めません。
メール本文と署名データを別にして、メール本文をplaintextで送る手法です。

node-forgeで対応しようとしたが断念

node-forge

PKCS#7 という形式で署名データを生成するらしいが、クリアテキスト署名の生成方法がわからず。

PKCS #7 分離署名

3.4.3 multipart/signed フォーマットを使った署名

さらに調べていくと、PKCS #7 分離署名という言葉を見つける。

detached mode

node-forgeのReadmeを読んでいると、detached modeという記載を見つける。
おそらくこれが分離署名にあたるのではないかと仮定し、実装してみる。

// PKCS#7 Sign in detached mode.
// Includes the signature and certificate without the signed data.
p7.sign({detached: true});

node-forgeのIssuesに、detached modeについて言及しているものがあった

PKCS#7 detached is not that much detached #607

内容を読んでみると、detached modeに不具合があるもよう。
Closedになってるのでもう直っているのかなと思い、該当のソースを確認してみると…

// TODO: optimize away duplication

いや直ってないんかいwww
node-forgeは諦めよう。

openssl_pkcs7_sign() というPHPの関数を見つける

$message = realpath('message.txt');
$sign = realpath('sign.txt');
$cert = 'file://' .realpath('./cert.txt');
$key = 'file://' .realpath('./key.txt');
$headers = [
    'signing-time' => (new DateTime())->format('o-m-d H:i:s'),
];

$certfile = file_get_contents($cert);
$pkeyfile = file_get_contents($key);

openssl_pkcs7_sign($message, $sign, $certfile, array($pkeyfile, ''), $headers, PKCS7_TEXT | PKCS7_DETACHED);

出力される「sign.txt」の中身をそのままRawMessageとして、 ses.sendRawEmail()を送信したところうまくいった。
この関数はおそらく裏でopensslコマンドを実行しているだけだと思われる。
Lambdaでopensslを使えば解決するのでは。

Lambdaでopensslコマンドを使用する

AWS LambdaでOpenSSLを使う方法

参考サイトのままだとうまくいかないので補足します。

opensslに実行権限を付与

opensslに実行権限を付与してからzipを生成すること。

zipの作り方に注意

opensslをディレクトリに入れてzipを作ると階層が1つ深くなってしまうので、以下の方法でzipを生成すること

zip -r openssl.zip openssl

最終的にこんなコードになりました。

const execSync = require('child_process').execSync

const toAddresses = '送信先メールアドレス'
const cert = 'PEM形式の証明書'
const privateKey = 'PEM形式の秘密鍵'
let mailBody = 'メール本文'

const certPath = '/tmp/cert.txt'
fs.writeFileSync(certPath, cert)

const privateKeyPath = '/tmp/privateKey.txt'
fs.writeFileSync(privateKeyPath, privateKey)

mailBody = 'Content-Type: text/plain; charset=ISO-2022-JP\r\n\r\n' + mailBody 

const mailBodyPath = '/tmp/mailBody.txt'
fs.writeFileSync(mailBodyPath, mailBody )

// opensslコマンドで署名データ生成
const opensslCommand = `/opt/openssl smime -pk7out -sign -in ${mailBodyPath} -signer ${certPath} -inkey ${privateKeyPath}  -md SHA256 -from "${sender}" -to "${toAddresses}" -subject "${subject}"`
const rawMessage = execSync(opensslCommand).toString()

const eParams = {
  Destinations: [toAddresses],
  RawMessage: {
    Data: Buffer.from(rawMessage),
  },
  Source: sender,
}

await ses.sendRawEmail(eParams).promise()

おそらく-pk7out が分離署名にあたるのではないかと思われます。
証明書などのデータはファイルから読み込む方法しかないようなので、無理やりですが、/tmp/ディレクトリに保存して読み込んでいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む