20210123のNode.jsに関する記事は13件です。

discord.jsでメンションするとmp3ファイルを再生するbot

はじめに

実装/実行環境: glitch.com : https://glitch.com/
定期bot監視: UptimeRobot : https://uptimerobot.com/
開発環境: Node.js
discord API: v12

botの作成等はこちらの記事を参考にしてください(コーディング部分以外)
https://note.com/bami55/n/ncc3a68652697#KRily

この記事の現バージョンより古いコードや、今回作成するbotのための追加作業を敵船説明していきます。
※ botの招待でURLの作成時にパーミッションをAdminにするのは嫌!!という方は、
  下記画像のようにBOT PERMISSIONSを設定してください
disco.png

・View Channels: プライベートチャンネルの閲覧許可 (全チャンネルに対応させるため)
・Manage Messages: メッセージの管理許可 (メッセージ削除を利用するため)
・Read Message History: メッセージの閲覧許可 (メンションの取得のため)
・Connect: 接続許可 (ボイスチャットルームに入るため)
・Speak: 音声再生許可 (音声を流すため)

コード

まず、ボイスチャットをbotが利用するには@discordjs/opusというパッケージが必要なので、
package.jsonを開き、上部のAdd Package@discordjs/opusと打ち込んでインポートしてください。
※数分かかるので気長にお待ちください


次に、assetsを開き、Upload an Asset↑を押して音声をアップロードしてください。
ここでアップロードしたファイルをクリックすることで、そのURLが取得できます。


次に、コードについてです。下記に僕の書いたコード全体を乗せます。

main.js
// Response for Uptime Robot
const http = require("http");
http
  .createServer(function(request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("Discord bot is active now \n");
  })
  .listen(3000);

// Discord bot implements
const discord = require("discord.js");
const client = new discord.Client();

client.on("ready", message => {
  client.user.setActivity("メンションで歓声");
  console.log("bot is ready!");
});

// メッセージ取得時
client.on("message", message => {
  // botのメンション かつ メンションしたユーザがvcチャネルに入っているかどうか
  if (message.mentions.has(client.user) && message.member.voice.channel) {
    // ボイチャに参加
    message.member.voice.channel
      .join()
      .then(conn => {
        // メンションメッセージを削除
        message.delete();
        // assetsに追加したmp3ファイルの再生インスタンス
        const dispatcher = conn.play("assetsから再生したいmp3ファイルの URLをコピーして貼る");
        // 音量調節
        dispatcher.setVolume(0.5);
        // 再生終了時にボイチャを抜ける
        dispatcher.on("finish", reason => {
          conn.disconnect();
        });
      })
      .catch(console.log);
    return;
  }
});

if (process.env.DISCORD_BOT_TOKEN == undefined) {
  console.log("please set ENV: DISCORD_BOT_TOKEN");
  process.exit(0);
}

client.login(process.env.DISCORD_BOT_TOKEN);

ready部分

まず、botのアクティビティ表示のためにuser.setActivity()を利用しています。
v11ではuser.setPresence()だったようです。

message部分

最初のif文で、メンションされた人物が当該botであるかの判定にmentions.has()を利用しています。
v11ではisMemberMentioned()だったようです。

また、メンションしたユーザーの所在voiceチャンネルの取得にmember.voice.channelを利用しています。
v11ではmember.voiceChannelだったようです。

assetsのファイル(.mp3)の再生にplay()を利用しています。
v11ではplayFile()だったようです。

次に、音量調節としてsetVolume()を利用しています。

また、再生ファイルの終了判定で"finish"としていますが、v11ではendだったようです。

最後に

メンションをbotが自動で消すようにしましたが、このままではタイミングが悪いと異なるメッセージが
消去される可能性がありますので、bot自身を指すメンションであるか確認した方がいいです。

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

Node.js + Spring BootでTOTP認証を実装してみた

少し暇な時間ができたので気まぐれにTOTP(Time-based One-Time Password)認証を実装してみた。
TOTP認証に関しては今更言うまでもないが、時間限定の一時パスコードにより二段階認証である。代表的なアプリとしては、Google AuthenticatorやMicrosoft Authenticatorがある。

念のため、TOTP認証における認証の流れ/アルゴリズムは以下の通り。

TOTP生成アルゴリズム :
1. 現在時刻(UNIXタイムスタンプ)/有効期限(秒)の値をクライアント/サーバの双方で保持している秘密鍵でHMAC値を算出
2. 「1.」でHMAC値における20文字目の値から、下位4ビットを取り出す。言い換えれば、20文字目のビットに「00001111」とAND演算を実施
3. 「2.」で算出した値を10進数変換し、当該値をoffsetとする
4. 「3.」で取得したoffsetの値から4文字分「1.」で算出したHMACから切り出す
5. 「4.」で取得した値に4文字分(=31ビット)に対して、31ビットなので「0x7FFFFFFF」とAND演算を実施
6. 「5.」で算出した値を10のTOTPの桁数分、べき乗した値の剰余計算

TOTP認証の流れ :
1. クライアントから端末識別情報とともに、上記アルゴリズムで算出されたTOTPをリクエスト
2. サーバー側で端末情報に紐づいた秘密鍵を取得
3. サーバー側で同様にTOTPを生成
4. 「1.」でリクエストされたTOTPと、「3.」で生成されたTOTPが一致していれば認証OK

今回、サーバーサイドはSpring Boot、クライアントサイドはNode.jsで実装した。現在時刻取得に関しては、クライアント/サーバー側でずれを無くすためにNTP(ntp.nict.jp)を使用。

① TOTP認証用API

package com.example.demo;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.jboss.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TOTPAuth {

    /** TOTP取得DAO */
    @Autowired
    private TotpDAO totpDAO;

    /** ハッシュアルゴリズム */
    private final static String HMAC_SHA1 = "HmacSHA1";

    /** TOTP桁数 */
    private final static int DIGIT = 6;

    /** TOTP有効期限 */
    private final static int STEP_TIME = 30;

    /** NTPドメイン */
    private final static String NTP_SERVER = "ntp.nict.jp";

    /** NTPクライアント */
    private final NTPUDPClient client = new NTPUDPClient();

    /** ロガー設定 */
    private final Logger log = Logger.getLogger(TOTPAuth.class);

    @RequestMapping(value = "/totp/check", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE
            + ";charset=UTF-8")
    public ResponseEntity<TotpCheckResDTO> execute(@RequestBody TotpLoginForm form)
            throws UnsupportedEncodingException {

        TotpCheckResDTO resDTO = new TotpCheckResDTO();

        log.debug(TOTPAuth.class.getSimpleName() + "#execute request totp value: " + form.getTotp());
        SecretDTO secretInfo = totpDAO.getSecret(form.getDeviceId());
        byte[] hmacByte = doHmac(secretInfo.getSecret());
        long totp = getTOTP(hmacByte);
        log.debug(TOTPAuth.class.getSimpleName() + "#execute server calculated totp value: " + totp);
        if (form.getTotp() == totp) {
            resDTO.setResultMsg("TOTP authentication success.");
            return new ResponseEntity<TotpCheckResDTO>(resDTO, null, HttpStatus.OK);
        }

        resDTO.setResultMsg("TOTP authentication failed.");
        return new ResponseEntity<TotpCheckResDTO>(resDTO, null, HttpStatus.FORBIDDEN);

    }

    /**
     * NTP時刻取得
     * 
     * @return NTP時刻
     * @throws IOException
     */
    private long getNtpTime() {

        long ntpTime;

        try {
            this.client.open();
            InetAddress host = InetAddress.getByName(NTP_SERVER);
            TimeInfo info = this.client.getTime(host);
            info.computeDetails();
            ntpTime = (System.currentTimeMillis() + info.getOffset()) / 1000L;
            log.debug(TOTPAuth.class.getSimpleName() + "#getNtpTime current time: " + ntpTime);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return ntpTime;
    }

    /**
     * HMAC値算出
     * 
     * @param secret
     * @return HMAC値
     */
    public byte[] doHmac(String secret) {

        byte[] hmacByte = null;
        try {
            Object msg = getNtpTime() / STEP_TIME;
            SecretKeySpec sk = new SecretKeySpec(secret.getBytes(), HMAC_SHA1);
            Mac mac = Mac.getInstance(HMAC_SHA1);
            mac.init(sk);
            hmacByte = mac.doFinal(msg.toString().getBytes());

        } catch (NoSuchAlgorithmException e) {
            log.error(TOTPAuth.class.getSimpleName()
                    + "#doHmac NoSuchAlgorithmException occurred, failed to create hmac hash value");
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            log.error(TOTPAuth.class.getSimpleName()
                    + "#doHmac InvalidKeyException occurred, failed to create hmac hash value");
            throw new RuntimeException(e);
        }

        return hmacByte;
    }

    /**
     * TOTP取得
     * 
     * @param hmacByte HMAC値
     * @return TOTP
     */
    public long getTOTP(byte[] hmacByte) {

        int offset = hmacByte[19] & 0xF;
        ByteBuffer result = ByteBuffer.wrap(hmacByte, offset, offset + 4);
        int p = result.getInt() & 0x7FFFFFFF;
        long totp = (long) (p % Math.pow(10, DIGIT));
        log.debug(TOTPAuth.class.getSimpleName() + "#getTOTP created totp value: " + totp);

        return totp;

    }

}

② 秘密鍵取得DAO

・Redisの構成は以下の通り
Key: 端末ID
Field: secret
Value: 秘密鍵

package com.example.demo;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class TotpDAO {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    public SecretDTO getSecret(String deviceId) {

        ObjectMapper mapper = new ObjectMapper();

        SecretDTO resDTO = null;
        try {
            Map<Object, Object> secretInfo = redisTemplate.opsForHash().entries(deviceId);
            resDTO = mapper.readValue(mapper.writeValueAsString(secretInfo), SecretDTO.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }       
        return resDTO;       
    }   
}

③ RESTクライアント

const crypto = require('crypto')
const ntpClient = require('ntp-client');

const key = 'mysecret'
const digit = 6;
const step_time = 30;
const Client = require('node-rest-client').Client;
const client = new Client();
const totpUrl = "http://localhost:8080/totp/check";


//TOTP認証実行
function execute(){
  new Promise((resolve, reject) => {
  ntpClient.getNetworkTime("ntp.nict.jp", 123, function(err, date){
        var currentDate = date.getTime();
        var msg = Math.floor( currentDate /1000/ step_time);
        console.log("HMAC msg: " + msg);
        const bufferArray = doHmac(key, msg);
        let totp = truncate(bufferArray);
        console.log("totp: " + totp);
        checkTotp(totp);
    })
  })
}

//TOTP認証リクエスト
function checkTotp(totp){

  const args = {
    data: {
      deviceId: '1a2b3c',
      totp: totp
  },
  headers: { "Content-Type": "application/json" }
}

  client.post(totpUrl, args, function (data, res) {
      console.log(data)
  })
}

//JMACのHEX値を取得
function doHmac(secret, currentTime){
  const hex = crypto.createHmac('sha1', secret)
    .update(String(currentTime))
    .digest('hex');

  return hex;
}

//truncate処理
function truncate(hmac){

  var hmacArray = hmac.match(/.{2}/g);
  var hexVal = '0x' + hmacArray[19].toString(16).toUpperCase()
  const offset = hexVal & 0xF;
  const p = '0x' + hmacArray.slice(offset, offset+4).join("").toString(16).toUpperCase();
  const sNum = p & 0x7FFFFFFF;
  return sNum % Math.pow(10, digit);
}

//実行
execute();

認証が成功すれば以下API応答が返却される。

HMAC msg value: 53713602
totp: 720527
{ resultMsg: 'TOTP authentication success.' }
[Finished in 7.125s]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel-mixのコンパイルでSyntaxError: Unexpected token = と怒られたら

発生したエラー

laravel-mixでコンパイルしたところ下記のエラーで怒られた
触ってないファイルでsyntax errorとか言われて困った

ubuntu
# npm run dev
npm WARN npm npm does not support Node.js v10.23.1
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 4, 6, 7, 8, 9.
npm WARN npm You can find the latest version at https://nodejs.org/

> @ dev /var/www/vue-laravel-spa
> npm run development

npm WARN npm npm does not support Node.js v10.23.1
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 4, 6, 7, 8, 9.
npm WARN npm You can find the latest version at https://nodejs.org/

> @ development /var/www/vue-laravel-spa
> mix

[webpack-cli] /var/www/vue-laravel-spa/node_modules/laravel-mix/src/Mix.js:18
    static _primary = null;
                    ^

SyntaxError: Unexpected token =
    at new Script (vm.js:83:7)
    at NativeCompileCache._moduleCompile (/var/www/vue-laravel-spa/node_modules/v8-compile-cache/v8-compile-cache.js:240:18)
    at Module._compile (/var/www/vue-laravel-spa/node_modules/v8-compile-cache/v8-compile-cache.js:184:36)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Module.require (internal/modules/cjs/loader.js:692:17)
    at require (/var/www/vue-laravel-spa/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at module.exports (/var/www/vue-laravel-spa/node_modules/laravel-mix/setup/webpack.config.js:2:17)
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! @ development: `mix`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the @ development script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-01-23T09_26_30_393Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! @ dev: `npm run development`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the @ dev script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-01-23T09_26_30_443Z-debug.log

環境

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H2

% docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.1
 Version:           19.03.13
 API version:       1.40

# php -v
PHP 7.4.7 (cli) (built: Jun 11 2020 18:41:17) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Xdebug v3.0.2, Copyright (c) 2002-2021, by Derick Rethans

# php artisan -v
Laravel Framework 8.24.0

原因

ググりまくった結果こちらのサイトに辿り着いた
https://github.com/JeffreyWay/laravel-mix/issues/2570
どうやらnode.jsのバージョンを最新にすると直るらしい

対処

こちらの記事を参考に対処した
https://qiita.com/seibe/items/36cef7df85fe2cefa3ea
https://qiita.com/kiwi-bird/items/e3e551938d09282cf4ee
今回初めて知ったのだがn packageを使うことでnpmパッケージとnode.js自体のバージョン管理をしてくれるらしい
こんな便利なものがあったのか

というわけでn packageをインストールする

ubuntu
# npm install n -g  # グローバルにインストールする
npm WARN npm npm does not support Node.js v10.23.1
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 4, 6, 7, 8, 9.
npm WARN npm You can find the latest version at https://nodejs.org/
/usr/local/bin/n -> /usr/local/lib/node_modules/n/bin/n
+ n@7.0.0
added 1 package from 4 contributors in 0.858s

# n stable  # 最新のnodejsをインストール
  installing : node-v14.15.4
       mkdir : /usr/local/n/versions/node/14.15.4
       fetch : https://nodejs.org/dist/v14.15.4/node-v14.15.4-linux-x64.tar.xz
   installed : v14.15.4 (with npm 6.14.10)

Note: the node command changed location and the old location may be remembered in your current shell.
         old : /usr/bin/node
         new : /usr/local/bin/node
To reset the command location hash either start a new shell, or execute PATH="$PATH"

# apt purge -y nodejs npm   # aptで入れた古いnodejsとnpmを削除
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  gyp javascript-common libbrotli1 libc-ares2 libexpat1 libjs-inherits libjs-is-typedarray libnode-dev libnode64 libpython-stdlib
  libpython2-stdlib libpython2.7-minimal libpython2.7-stdlib libreadline7 libssl-dev libuv1 libuv1-dev lsb-base mime-support nodejs-doc python
  python-minimal python-pkg-resources python2 python2-minimal python2.7 python2.7-minimal
Use 'apt autoremove' to remove them.
The following packages will be REMOVED:
  node-abbrev* node-ajv* node-ansi* node-ansi-align* node-ansi-regex* node-ansi-styles* node-ansistyles* node-aproba* node-archy*
  node-are-we-there-yet* node-asn1* node-assert-plus* node-asynckit* node-aws-sign2* node-aws4* node-balanced-match* node-bcrypt-pbkdf*
  node-bluebird* node-boxen* node-brace-expansion* node-builtin-modules* node-builtins* node-cacache* node-call-limit* node-camelcase*
  node-caseless* node-chalk* node-chownr* node-cli-boxes* node-cliui* node-clone* node-co* node-color-convert* node-color-name*
  node-combined-stream* node-concat-map* node-concat-stream* node-config-chain* node-console-control-strings* node-copy-concurrently*
  node-core-util-is* node-cross-spawn* node-cyclist* node-dashdash* node-decamelize* node-decompress-response* node-deep-extend* node-defaults*
  node-delayed-stream* node-delegates* node-detect-indent* node-detect-newline* node-duplexer3* node-duplexify* node-ecc-jsbn* node-editor*
  node-encoding* node-end-of-stream* node-errno* node-escape-string-regexp* node-execa* node-extend* node-extsprintf* node-find-up*
  node-flush-write-stream* node-forever-agent* node-form-data* node-from2* node-fs-vacuum* node-fs-write-stream-atomic* node-fs.realpath*
  node-gauge* node-get-caller-file* node-get-stream* node-getpass* node-glob* node-got* node-graceful-fs* node-gyp* node-har-schema*
  node-har-validator* node-has-flag* node-has-symbol-support-x* node-has-to-string-tag-x* node-has-unicode* node-hosted-git-info*
  node-http-signature* node-iconv-lite* node-iferr* node-import-lazy* node-imurmurhash* node-inflight* node-inherits* node-ini* node-invert-kv*
  node-is-builtin-module* node-is-npm* node-is-object* node-is-plain-obj* node-is-retry-allowed* node-is-stream* node-is-typedarray*
  node-isarray* node-isexe* node-isstream* node-isurl* node-jsbn* node-json-parse-better-errors* node-json-schema* node-json-stable-stringify*
  node-json-stringify-safe* node-jsonify* node-jsonparse* node-jsonstream* node-jsprim* node-latest-version* node-lazy-property* node-lcid*
  node-libnpx* node-locate-path* node-lockfile* node-lowercase-keys* node-lru-cache* node-mem* node-mime-types* node-mimic-fn*
  node-mimic-response* node-minimatch* node-minimist* node-mississippi* node-mkdirp* node-move-concurrently* node-mute-stream* node-node-uuid*
  node-nopt* node-normalize-package-data* node-npm-package-arg* node-npm-run-path* node-npmlog* node-oauth-sign* node-object-assign* node-once*
  node-opener* node-os-locale* node-osenv* node-p-cancelable* node-p-finally* node-p-limit* node-p-locate* node-p-timeout* node-package-json*
  node-parallel-transform* node-path-exists* node-path-is-absolute* node-path-is-inside* node-performance-now* node-prepend-http*
  node-process-nextick-args* node-promise-inflight* node-promzard* node-proto-list* node-prr* node-pump* node-pumpify* node-punycode* node-qs*
  node-qw* node-rc* node-read* node-read-package-json* node-readable-stream* node-registry-auth-token* node-registry-url* node-request*
  node-require-directory* node-require-main-filename* node-resolve-from* node-retry* node-rimraf* node-run-queue* node-safe-buffer* node-semver*
  node-semver-diff* node-set-blocking* node-sha* node-shebang-command* node-shebang-regex* node-signal-exit* node-slash* node-slide*
  node-sorted-object* node-spdx-correct* node-spdx-expression-parse* node-spdx-license-ids* node-sshpk* node-ssri* node-stream-each*
  node-stream-iterate* node-stream-shift* node-string-decoder* node-string-width* node-strip-ansi* node-strip-eof* node-strip-json-comments*
  node-supports-color* node-tar* node-term-size* node-text-table* node-through* node-through2* node-timed-out* node-tough-cookie*
  node-tunnel-agent* node-tweetnacl* node-typedarray* node-uid-number* node-unique-filename* node-unpipe* node-url-parse-lax*
  node-url-to-options* node-util-deprecate* node-uuid* node-validate-npm-package-license* node-validate-npm-package-name* node-verror*
  node-wcwidth.js* node-which* node-which-module* node-wide-align* node-widest-line* node-wrap-ansi* node-wrappy* node-write-file-atomic*
  node-xdg-basedir* node-xtend* node-y18n* node-yallist* node-yargs* node-yargs-parser* nodejs* npm*
0 upgraded, 0 newly installed, 241 to remove and 18 not upgraded.
After this operation, 16.7 MB disk space will be freed.
(Reading database ... 19266 files and directories currently installed.)
Removing npm (5.8.0+ds6-4+deb10u2) ...
Removing node-gyp (3.8.0-6) ...
Removing node-nopt (3.0.6-3) ...
Removing node-abbrev (1.1.1-1) ...
Removing node-request (2.88.1-2) ...
Removing node-har-validator (5.1.0-1) ...
Removing node-ajv (5.0.0-1) ...
Removing node-ansi (0.3.0-3) ...
Removing node-boxen (1.2.2-1) ...
Removing node-ansi-align (2.0.0-1) ...
Removing node-libnpx (10.2.0+repack-1) ...
Removing node-yargs (10.0.3-2) ...
Removing node-cliui (4.1.0-1) ...
Removing node-wrap-ansi (4.0.0-1) ...
Removing node-chalk (2.3.0-2) ...
Removing node-ansi-styles (3.2.1-1) ...
Removing node-ansistyles (0.1.3-1) ...
Removing node-npmlog (4.1.2-1) ...
Removing node-gauge (2.7.4-1) ...
Removing node-cacache (11.3.2-2) ...
Removing node-move-concurrently (1.0.1-2) ...
Removing node-archy (1.0.0-2) ...
Removing node-are-we-there-yet (1.1.4-1) ...
Removing node-http-signature (1.2.0-1) ...
Removing node-sshpk (1.13.1+dfsg-2) ...
Removing node-asn1 (0.2.3-1) ...
Removing node-dashdash (1.14.1-2) ...
Removing node-jsprim (1.4.0-1) ...
Removing node-verror (1.10.0-1) ...
Removing node-form-data (2.3.2-2) ...
Removing node-asynckit (0.4.0-2) ...
Removing node-aws-sign2 (0.7.1-1) ...
Removing node-aws4 (1.8.0-1) ...
Removing node-read-package-json (2.0.13-1) ...
Removing node-copy-concurrently (1.0.5-4) ...
Removing node-bcrypt-pbkdf (1.0.1-1) ...
Removing node-bluebird (3.5.1+dfsg2-2) ...
Removing node-normalize-package-data (2.4.0-1) ...
Removing node-is-builtin-module (2.0.0-1) ...
Removing node-builtin-modules (3.0.0-1) ...
Removing node-npm-package-arg (6.0.0-2) ...
Removing node-validate-npm-package-name (3.0.0-1) ...
Removing node-builtins (1.0.3-1) ...
Removing node-call-limit (1.1.0-1) ...
Removing node-yargs-parser (11.1.1-1+deb10u1) ...
Removing node-camelcase (5.0.0-1) ...
Removing node-caseless (0.12.0-1) ...
Removing node-tar (4.4.6+ds1-3) ...
Removing node-chownr (1.1.1-1) ...
Removing node-cli-boxes (1.0.0-1) ...
Removing node-widest-line (1.2.2-1) ...
Removing node-co (4.6.0-1) ...
Removing node-color-convert (1.9.0-3) ...
Removing node-color-name (1.1.3-1) ...
Removing node-combined-stream (1.0.7-1) ...
Removing node-mississippi (3.0.0-1) ...
Removing node-concat-stream (1.6.2-1) ...
Removing node-config-chain (1.1.11-1) ...
Removing node-console-control-strings (1.1.0-1) ...
Removing node-through2 (2.0.5-2) ...
Removing node-flush-write-stream (1.0.3-1) ...
Removing node-term-size (1.2.0+dfsg-2) ...
Removing node-os-locale (2.0.0-1) ...
Removing node-execa (0.10.0+dfsg-1) ...
Removing node-cross-spawn (5.1.0-2) ...
Removing node-parallel-transform (1.1.0-2) ...
Removing node-cyclist (1.0.1-2) ...
Removing node-decamelize (1.2.0-1) ...
Removing node-latest-version (3.1.0-1) ...
Removing node-package-json (4.0.1-1) ...
Removing node-got (7.1.0-1) ...
Removing node-decompress-response (3.3.0-1) ...
Removing node-registry-url (3.1.0-1) ...
Removing node-registry-auth-token (3.3.1-1) ...
Removing node-rc (1.1.6-2) ...
Removing node-deep-extend (0.4.1-2) ...
Removing node-delayed-stream (0.0.5-1) ...
Removing node-delegates (1.0.0-1) ...
Removing node-detect-indent (5.0.0-1) ...
Removing node-detect-newline (2.1.0-1) ...
Removing node-duplexer3 (0.1.4-4) ...
Removing node-pumpify (1.5.1-1) ...
Removing node-duplexify (3.6.1-1) ...
Removing node-ecc-jsbn (0.1.1-1) ...
Removing node-editor (1.0.0-1) ...
Removing node-encoding (0.1.12-2) ...
Removing node-stream-each (1.2.2-2) ...
Removing node-pump (3.0.0-1) ...
Removing node-end-of-stream (1.4.1-1) ...
Removing node-errno (0.1.4-1) ...
Removing node-escape-string-regexp (1.0.5-1) ...
Removing node-extend (3.0.2-1) ...
Removing node-extsprintf (1.3.0-1) ...
Removing node-find-up (2.1.0-1) ...
Removing node-forever-agent (0.6.1-1) ...
Removing node-from2 (2.3.0-1) ...
Removing node-fs-vacuum (1.2.10-2) ...
Removing node-fs-write-stream-atomic (1.0.10-4) ...
Removing node-get-caller-file (1.0.2-1) ...
Removing node-get-stream (3.0.0-1) ...
Removing node-getpass (0.1.7-1) ...
Removing node-write-file-atomic (2.3.0-1) ...
Removing node-sha (2.0.1-1) ...
Removing node-graceful-fs (4.1.11-1) ...
Removing node-har-schema (2.0.0-1) ...
Removing node-supports-color (4.4.0-2) ...
Removing node-has-flag (2.0.0-1) ...
Removing node-isurl (1.0.0-1) ...
Removing node-has-to-string-tag-x (1.4.1+dfsg-1) ...
Removing node-has-symbol-support-x (1.4.1+dfsg-1) ...
Removing node-has-unicode (2.0.1-2) ...
Removing node-hosted-git-info (2.7.1-1) ...
Removing node-iconv-lite (0.4.13-2) ...
Removing node-iferr (1.0.2-1) ...
Removing node-import-lazy (3.0.0.REALLY.2.1.0-1) ...
Removing node-unique-filename (1.1.0+ds-2) ...
Removing node-imurmurhash (0.1.4-1) ...
Removing node-ini (1.3.5-1) ...
Removing node-lcid (1.0.0-1) ...
Removing node-invert-kv (1.0.0-1) ...
Removing node-is-npm (1.0.0-1) ...
Removing node-is-object (1.0.1-1) ...
Removing node-is-plain-obj (1.1.0-1) ...
Removing node-is-retry-allowed (1.1.0-1) ...
Removing node-is-stream (1.1.0-1) ...
Removing node-is-typedarray (1.0.0-2) ...
Removing node-which (1.3.0-2) ...
Removing node-isexe (2.0.0-4) ...
Removing node-isstream (0.1.2+dfsg-1) ...
Removing node-jsbn (1.1.0-1) ...
Removing node-json-parse-better-errors (1.0.2-2) ...
Removing node-json-schema (0.2.3-1) ...
Removing node-json-stable-stringify (1.0.1-1) ...
Removing node-json-stringify-safe (5.0.1-1) ...
Removing node-jsonify (0.0.0-1) ...
Removing node-jsonstream (1.3.2-1) ...
Removing node-jsonparse (1.3.1-6) ...
Removing node-lazy-property (1.0.0-3) ...
Removing node-locate-path (2.0.0-1) ...
Removing node-lockfile (1.0.4-1) ...
Removing node-lowercase-keys (1.0.0-2) ...
Removing node-lru-cache (5.1.1-4) ...
Removing node-mem (1.1.0-1) ...
Removing node-mime-types (2.1.21-1) ...
Removing node-mimic-fn (1.1.0-1) ...
Removing node-mimic-response (1.0.0-1) ...
Removing node-minimist (1.2.0-1+deb10u1) ...
Removing node-mkdirp (0.5.1-1) ...
Removing node-promzard (0.3.0-1) ...
Removing node-read (1.0.7-1) ...
Removing node-mute-stream (0.0.8-1) ...
Removing node-node-uuid (3.3.2-2) ...
Removing node-npm-run-path (2.0.2-2) ...
Removing node-oauth-sign (0.9.0-1) ...
Removing node-object-assign (4.1.1-2) ...
Removing node-opener (1.4.3-1) ...
Removing node-osenv (0.1.5-1) ...
Removing node-p-cancelable (0.3.0-1) ...
Removing node-p-timeout (1.2.0-1) ...
Removing node-p-finally (1.0.0-2) ...
Removing node-p-locate (2.0.0-1) ...
Removing node-p-limit (1.1.0-1) ...
Removing node-path-exists (3.0.0-1) ...
Removing node-path-is-inside (1.0.2-1) ...
Removing node-performance-now (2.1.0+debian-1) ...
Removing node-url-parse-lax (1.0.0-1) ...
Removing node-prepend-http (2.0.0-1) ...
Removing node-promise-inflight (1.0.1-1) ...
Removing node-proto-list (1.2.4-1) ...
Removing node-prr (1.0.1-1) ...
Removing node-tough-cookie (2.3.4+dfsg-1) ...
Removing node-punycode (2.1.1-2) ...
Removing node-qs (6.5.2-1) ...
Removing node-qw (1.0.1-1) ...
Removing node-require-directory (2.1.1-1) ...
Removing node-require-main-filename (1.0.1-1) ...
Removing node-resolve-from (4.0.0-1) ...
Removing node-retry (0.10.1-1) ...
Removing node-tunnel-agent (0.6.1-1) ...
Removing node-semver-diff (2.1.0-2) ...
Removing node-semver (5.5.1-1) ...
Removing node-set-blocking (2.0.0-1) ...
Removing node-shebang-command (1.2.0-1) ...
Removing node-shebang-regex (2.0.0-1) ...
Removing node-signal-exit (3.0.2-1) ...
Removing node-slash (1.0.0-1) ...
Removing node-slide (1.1.6-2) ...
Removing node-sorted-object (2.0.1-1) ...
Removing node-validate-npm-package-license (3.0.1-1) ...
Removing node-spdx-correct (1.0.2-1) ...
Removing node-spdx-expression-parse (1.0.4-1) ...
Removing node-spdx-license-ids (1.2.2-1) ...
Removing node-ssri (5.2.4-2) ...
Removing node-stream-iterate (1.2.0-4) ...
Removing node-stream-shift (1.0.0-1) ...
Removing node-strip-eof (1.0.0-2) ...
Removing node-strip-json-comments (2.0.1-2) ...
Removing node-text-table (0.2.0-2) ...
Removing node-through (2.3.8-1) ...
Removing node-timed-out (4.0.1-4) ...
Removing node-tweetnacl (0.14.5+dfsg-3) ...
Removing node-typedarray (0.0.6-1) ...
Removing node-uid-number (0.0.6-1) ...
Removing node-unpipe (1.0.0-1) ...
Removing node-url-to-options (1.0.1-1) ...
Removing node-uuid (3.3.2-2) ...
Removing node-which-module (2.0.0-1) ...
Removing node-wide-align (1.1.0-1) ...
Removing node-xdg-basedir (3.0.0-1) ...
Removing node-xtend (4.0.1-2) ...
Removing node-y18n (3.2.1-2) ...
Removing node-yallist (3.0.3-1) ...
Removing node-run-queue (1.0.3-1) ...
Removing node-aproba (1.2.0-1) ...
Removing node-assert-plus (1.0.0-1) ...
Removing node-rimraf (2.6.2-1) ...
Removing node-glob (7.1.3-2) ...
Removing node-minimatch (3.0.4-3) ...
Removing node-brace-expansion (1.1.8-1) ...
Removing node-balanced-match (0.4.2-1) ...
Removing node-string-width (2.1.1-1) ...
Removing node-wcwidth.js (1.0.0-1) ...
Removing node-defaults (1.0.3-1) ...
Removing node-clone (2.1.2-1) ...
Removing node-concat-map (0.0.1-1) ...
Removing node-readable-stream (2.3.6-1) ...
Removing node-core-util-is (1.0.2-1) ...
Removing node-fs.realpath (1.0.0-1) ...
Removing node-inflight (1.0.6-1) ...
Removing node-inherits (2.0.3-1) ...
Removing node-isarray (2.0.4-1) ...
Removing node-once (1.4.0-3) ...
Removing node-path-is-absolute (1.0.0-1) ...
Removing node-process-nextick-args (2.0.0-1) ...
Removing node-string-decoder (1.2.0-1) ...
Removing node-safe-buffer (5.1.2-1) ...
Removing node-util-deprecate (1.0.2-1) ...
Removing node-wrappy (1.0.2-1) ...
Removing node-strip-ansi (4.0.0-1) ...
Removing node-ansi-regex (3.0.0-1) ...
Removing nodejs (10.23.1~dfsg-1~deb10u1) ...
(Reading database ... 14507 files and directories currently installed.)
Purging configuration files for npm (5.8.0+ds6-4+deb10u2) ...

##### 一度ログインしなおす #######

# npm run dev   # いざコンパイル

> @ dev /var/www/vue-laravel-spa
> npm run development


> @ development /var/www/vue-laravel-spa
> mix

    Additional dependencies must be installed. This will only take a moment.

    Running: npm install vue-loader@^15.9.5 --save-dev --legacy-peer-deps

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.13 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.1 (node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.1: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

    Finished. Please run Mix again.


# npm run dev   # againとのことなので

> @ dev /var/www/vue-laravel-spa
> npm run development


> @ development /var/www/vue-laravel-spa
> mix

99% done plugins BuildOutputPlugin



   Laravel Mix v6.0.10   


✔ Compiled Successfully in 16791ms
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─────────┐
│                                                                                                                                  File │ Size    │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────┤
│                                                                                                                            /js/app.js │ 1.4 MiB │
│                                                                                                                           css/app.css │ 179 KiB │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────┘

成功しました
/public/js/app.js/public/css/app.cssが生成されました

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

Node.js: Let's Encrypt の証明書を使ったブローカーに接続

こちらで作成したブローカーに接続する方法です。
Mosquitto で Let's Encrypt の証明書を使う

.env
HOST=abc.example.com
TOPIC=sensors/topic_1
publish_ca.js
#! /usr/bin/node
// ---------------------------------------------------------------
//  publish_ca.js
//
//                  Jan/23/2021
//
// ---------------------------------------------------------------
'use strict'
const mqtt = require('mqtt')
const fs = require('fs')
const dotenv = require('dotenv')
// ---------------------------------------------------------------
console.error ("*** 開始 ***")
dotenv.config()
const host = `${process.env.HOST}`
const topic = `${process.env.TOPIC}`

const caFile = fs.readFileSync("/etc/ssl/certs/ca-certificates.crt")
const options = {
    rejectUnauthorized : false,
    ca:caFile 
}

const client  = mqtt.connect('mqtts://' + host + ':8883',options)

client.on('connect', function () {
    const today = new Date ()
    var ddx = (1900 + today.getYear ()) + "-" + (today.getMonth () +1)
    ddx += "-" + today.getDate () + " " + today.getHours()
    ddx += ":" + today.getMinutes () + ":" + today.getSeconds()
    client.publish(topic, ddx)
    console.log(ddx)
    client.publish(topic, 'Good Afternoon mqtts')
    client.publish(topic, 'こんにちは')
    client.end()
    console.error ("*** 終了 ***")
})

// ---------------------------------------------------------------

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./publish_ca.js
subscribe_ca.js
#! /usr/bin/node
// ---------------------------------------------------------------
//  subscribe.js
//
//                      Jan/23/2021
//
// ---------------------------------------------------------------
'use strict'
const mqtt = require('mqtt')
const fs = require('fs')
const dotenv = require('dotenv')

// ---------------------------------------------------------------
console.error ("*** 開始 ***")
dotenv.config()
const host = `${process.env.HOST}`
const topic = `${process.env.TOPIC}`

const caFile = fs.readFileSync("/etc/ssl/certs/ca-certificates.crt")
const options = {
    rejectUnauthorized : false,
    ca:caFile
}

const client  = mqtt.connect('mqtts://www2.ekzemplaro.org:8883',options)

client.on('connect', function () {
    client.subscribe(topic)
})

client.on('message', function (topic, message) {
  // message is Buffer
    console.log(message.toString())
})

// ---------------------------------------------------------------

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./subscribe_ca.js
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud Functions for Firebase で runtime を Node.js 12 にしてデプロイする方法

はじめに

Cloud Functions for Firebase でデプロイの際にnodeのバージョン周りでエラーが出てハマったのでまとめました。
簡潔に設定の仕方を紹介した後、ハマりどころを末尾に記載しておきます。

runtime を Node.js 12 にしてデプロイする方法

やり方は全部で2つあります。

1. package.jsonに記述する方法

以下のようにenginesに"node":"12"と記載する

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {

  },
  "engines": {
    "node":"12"
  },
  "dependencies": {

  },
  "devDependencies": {

  },
  "private": true
}

2. firebase.jsonに記述する方法

firebase functions をinitするとinitしたカレントディレクトリに firebase.jsonが作成されると思います。そこに以下のような記述を足すことでランタイムを指定できます!

{
  "functions": {
    "runtime": "nodejs12",
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint"
    ]
  }
}

v8.6.0で変更があったみたいです。 or と書いてあるように、package.jsonでやるやり方も使えます
image.png

公式リリースノート

ランタイム指定がうまくいった時のdeployのログ

以下のように、 creating Node.js 12 ~~~ と出ていれば指定できています!
image.png

大前提・要注意

1. FirebaseのプランをBlazeプラン(従量課金プラン)にすること

2. グローバルインストールされている firebase-toolsが最新のものであること!

これが古いままだと以下のようなエラーが出ます

Error: package.json in functions directory has an engines field which is unsupported. The only valid choices are: {"node": "8"} and {"node": "10"}.

3. 使用しているライブラリが必要とするnodeのバージョン要件をローカル環境が満たしていること!

deployなのでてっきりpackage.jsonに正しくランタイム指定されていればクラウド上で問題なくビルドしてくれると思ってましたが、どうやら自分が想像していたのとはビルドの仕組みが違うみたいで..(勉強します。)

自分は使用しているライブラリがnode v12以上を要求していたにも関わらず、ローカルのnodeのバージョンがv10系だったため、延々と以下のエラーが出てしまいました。

Error: There was an unknown problem while trying to parse function triggers. Please ensure you are using Node.js v6 or greater.

image.png

自分はnodebrewを使っていたので以下で解決しました。バージョンはご自身のライブラリの要件に合わせてしよしなに変えてください。

nodebrew use 12.20.1

新しく作った環境にfirebase-toolsがない場合は以下も実行

npm i -g firebase-tools

参考

公式リリースノート
Cloud Functions for FirebaseでNode.js 12を利用する方法

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

Node.jsがとっつきにくい言語だと思ったら ~非同期処理とは?~【初心者向け】

背景

JavaやらPythonやらの言語でエンジニア人生を送っていると、Node.jsは「何か違う~!!」と思う言語だと思う。今回はどんなところがとっつきにくい点なのか、他の言語と何が違うのかを書いていく:runner_tone5::thought_balloon:

Node.jsってどんな言語?

Node.jsは他の言語と比べて決定的に異なる点がある。「ノンブロッキングI/O」という処理方式を採用している点だ。ブロッキングとは、Node.jsのプログラムを実行する際に、ファイル呼び出しやネットワークリクエストなどNode.js以外の操作が完了するまで待たなければならない状態だ。ブロッキングが発生すると、プログラムの次の処理に進むことができない。Node.jsでは、このブロッキングを避けるため「ノンブロッキングI/O」という非同期処理を採用している。ファイル呼び出しやネットワークリクエストの処理の結果を待たずに次のプログラムコードが実行される。

const fs = require('fs'); // ファイル読込ライブラリ

// ファイル内容をコンソールに出力するコールバック関数
const fileoutput = (err, txt) => {
  console.log(txt); 
};

console.log('前');
fs.readFile('./readme.txt', 'utf8', fileoutput);
console.log('後');

上記のプログラムを実行すると、前、後、ファイルの中身という順番でコンソールに出力される。ファイル読込の処理結果を待たず、次のプログラムコードが実行されていることが分かるだろう。

なぜ非同期?

非同期的にプログラムを実行して、ブロッキングをなぜ避けなければいけないのかというと、Node.jsはシングルスレッドで動く処理だからだ。スレッドとは、プログラムを実行する実行単位のことだ。例えばWebサーバーにリクエストがきて、プログラムを実行する際に並列で実行できないとすると、シングルスレッドのためプログラムを実行する場所が他になく、大幅に処理が滞ってしまう。そのため、非同期で次の処理を実行できるようにしているのだ。そのため、メモリを効率的に利用できたり、プロセスのデッドロックが起こりにくいというメリットがある。

まとめ

Node.jsのほとんどの関数はこのように非同期で実施されるため、実行される順番には留意してコードを書こう:raised_hand:

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

node.jsのローカルサーバーでクライアントサイドjavascriptが動かない

はじめに

  • 素人がタイトルの問題で詰まったので解決方法を残しておきます
  • 解決方法の一つは@Suibari_chaさんの記事を大いに参考にさせてもらいましたが(ほぼそのままです)、「javascriptが動かない」でググると見つけづらかったので一応記載させていただきました

解決方法1

下記記事のSuibari_chaさんのcssを読み込む方法:Node.jsでhttpサーバを立てた際にCSSが読み取れない場合の対処法について

と同様に、javascriptのMIMEタイプを指定します。
サーバー起動時に「.jsはJavaScriptとして扱う」
というのを教えてあげないといけないということみたいです。
(node.jsなんだからそれくらいデフォルトで分かってくれよと思いますけども)

解決方法2

expressを使います

まず、expressをインストール

npm install express

下記のサーバー起動のスクリプトを書いて起動します。

index.js
// node index.js で起動
var express = require('express');
var app = express();

app.use(express.static(__dirname)); // ここに入っているjsやcssを使う

app.get('/', function (req, res) {
  //res.send('Hello World!');
  res.sendFile(__dirname + '/index.html');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});
//http://localhost:3000にアクセスして確認

こちらではいちいちMIMEタイプを指定しなくて良いみたいです。
ポイントはここです。

app.use(express.static(__dirname)); // ここに入っているjsやcssを使う

終わり

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

Docker x Express x PostgreSQL x Sequelize 環境構築メモ

検索したら意外とヒット少なかったので記事化

初学者ゆえ適切でないコードや表現があるかと思います。その際は優しくご指導いただけますと幸いです。

開発環境

  • macOS Catalina 10.15.7
  • Docker 20.10.2

作業ディレクトリ作成

Terminal
mkdir hoge/app
cd app
npm init

適当な場所に作業ディレクトリを作成&npm init
package nameとかは適当なものを選択。

server.js作成

Terminal
touch server.js
npm i --save express
server.js
const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(9000, () => {
  console.log("The app listening on port 5000");
});

nodemon設定

node server.jsだとserver.jsを書き換えるたびに再起動しなければならないため、nodemonを設定しておく。

Terminal
npm i --save nodemon
package.json
  "scripts": {
    "start": "nodemon server.js"
  }

node.jsのイメージを作成

node.jsの公式ドキュメントを参考にコンテナを作成する。

Dockerfile編集

Terminal
cd ..(appからhogeへ移動)
touch Dockerfile
Dockerfile
# ベースイメージ
FROM node:14.15.4

# 作業ディレクトリ
WORKDIR /usr/src

# packages.jsonを個別にコピーする理由は後述
COPY/packages*.json ./
RUN npm install

# その他のファイル群もコンテナへコピー
COPY / ./

EXPOSE 5000

# サーバースタート
CMD ["npm", "start"]

npm_modulesをignoreする

.dockerignore
npm_modules
npm-debug.log

npm_modulesをコピーするのは時間がかかるのでignoreして、先述のnpm installで作成する。

現在のhogeディレクトリ構成はこんな感じ

Screen Shot 2021-01-23 at 1.08.26.png

buildする

Terminal
docker build -t hoge/node .

-t: タグをつけるオプション(イメージの名前になる)

Terminal
Successfully built 英数字
Successfully tagged hoge/node:latest

↑こうなったらOK

コンテナ作成

Express、Postgresを組み合わせたコンテナを作成していく。

みんな大好きdocker-compose

Terminal
touch docker-compose.yml
touch .env
docker-compose.yml
version: "3"
services:
  app:
    # docker-compose up実行時に開始する関連サービス(上から順に実行)
    depends_on:
      - postgres
    # コンテナを実行するときの元となるimage(さっき作ったやつ)
    image: hoge/node
    # コンテナの名前
    container_name: "hoge_express-pg"
    # マウントするパス(ホストの相対パス:DockerfileのWORKDIRからの絶対パス)
    volumes:
      - ./app:/usr/src/app
    # ホストマシンにつなげるポート(ホスト:コンテナ)
    ports:
      - 5000:9000
    # 環境変数
    env_file: .env
    # コンテナの永続化
    tty: true

  postgres:
    image: postgres:13-alpine
    container_name: postgres
    ports:
      - 5432:5432
    volumes:
      - ./postgres/init:/docker-entrypoint-initdb.d
    env_file: .env
    hostname: postgres
    # rootにしないとあとで「権限ないで」って言われる
    user: root

  # ブラウザでdbを確認できるすごいヤツ(任意)
  pgadmin4:
    image: dpage/pgadmin4:4.20
    container_name: pgadmin4
    depends_on:
      - postgres
    ports:
      - 80:80
    volumes:
      - ./pgadmin:/var/lib/pgadmin/storage
    env_file: .env
    hostname: pgadmin4

volumesに指定したパスはホストマシンとコンテナとでファイルが共有されて便利。

.env
POSTGRES_USER=hoge
POSTGRES_PASSWORD=hoge
POSTGRES_INITDB_ARGS=--encoding=UTF-8

TZ=Asia/Tokyo

PGADMIN_DEFAULT_EMAIL=root
PGADMIN_DEFAULT_PASSWORD=root

POSTGRES_~: dbのログイン情報と設定
TZ: TimeZone
PGADMIN_DEFAULT_~: pgAdmin4のログイン情報

動作確認

Terminal
docker-compose up
Terminal
postgres  | 2021-01-23 01:24:24.380 JST [1] LOG:  database system is ready to accept connections
~
hoge_express-pg | [nodemon] starting `node server.js`
hoge_express-pg | The app listening on port 5000
~
pgadmin4  | [2021-01-22 16:24:29 +0000] [1] [INFO] Listening at: http://[::]:80

こんな感じに3つのコンテナの起動が確認できたらOK。

ブラウザ

ブラウザでlocalhost:5000にアクセスするとHello World!できる。
Screen Shot 2021-01-23 at 1.27.45.png

db作成

Ctrl+Cで一度コンテナを終了し、自動生成されたpostgres/initフォルダにSQLファイルを追加する。
ここに配置されたSQLはコンテナ開始時に実行されるので、db作成の他、初期データの設定にも便利。

Terminal
touch postgres/init/template.sql
postgres/init/template.sql
CREATE DATABASE development

作成したらもう一度docker-compose upしておく。

pgAdmin4初期設定(任意)

ブラウザでlocalhost:80にアクセス

Screen Shot 2021-01-23 at 2.02.39.png

こんな感じの画面が出るので、.envで設定したユーザー名とパスワードでログイン。

ちなみに、dpage/pgAdmin4:4は何故かこの段階でログインできない不具合があるようです(これに気づくのに一晩費やした)。

サーバー追加

ログインしたらAdd New ServerConnectionタブで
- Host: postgres
- Port: 5432
- Mentenance database: postgres
- Username: hoge
- Password: hoge

左のサーバー一覧にpostgresとその中にdevelopmentdbが表示されればOK。

もしコンテナ作成後に.envでパスワードなどを変更した場合は一度コンテナを削除してから再度接続を行う必要があるので注意。

Sequelize設定

ここからはコンテナ内で作業する(けどvolumesで共有されているのでほとんどの作業はホスト側でやっても平気←Docker使う意味は??
VSCodeでDockerの拡張機能を入れてると、ここから簡単にコンテナのbashを起動できる

Screen Shot 2021-01-23 at 1.30.34.png

各種パッケージインストール&Sequelize初期設定

/usr/src/app#
npm i --save sequelize sequelize-cli pg
npx sequelize-cli init

npx sequelize-cli initをすると、config, models, migrations, seedersフォルダが作成される

このディレクトリ名を見てわかるように、SequelizeはRubyのActiveRecordのようにdbのマイグレーションがJSでできるようになるスグレもの

config.json変更

/app/config/config.json
{
  "development": {
    "username": "hoge", ←.envで設定したdbのログイン情報
    "password": "hoge",
    "database": "development", ←dbの名前
    "host": "postgres",
    "dialect": "postgres"
  },
  "test": {
    省略
  },
  "production": {
    省略
  }
}

これでSequelizeの設定は完了。

Sequelizeでdbへアクセス

modelを作成

/usr/src/app#
npx sequelize-cli model:generate --name Post --attributes title:string,body:string

--name: model名
--attributes: modelが保有する属性(カラム)

migrations/日付-create-postmodels/post.jsの2つが自動生成されているのを確認する。

migrations/20210123053140-create-post
"use strict";
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable("Posts", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      title: {
        type: Sequelize.STRING,
      },
      body: {
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable("Posts");
  },
};
models/post.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Post extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Post.init(
    {
      title: DataTypes.STRING,
      body: DataTypes.STRING,
    },
    {
      sequelize,
      modelName: "Post",
    }
  );
  return Post;
};

マイグレーション

/usr/src/app#
npx sequelize-cli db:migrate
/usr/src/app#
> Sequelize CLI [Node: 14.15.4, CLI: 6.2.0, ORM: 6.4.0]

> Loaded configuration file "config/config.json".
> Using environment "development".
> == 20210123053140-create-post: migrating =======
> == 20210123053140-create-post: migrated (0.022s)

migratedとなればOK。

Expressからdbをさわってみる

Create

server.js
const express = require("express");
const app = express();

+ const models = require("./models/index");

app.get("/", (req, res) => {
  res.send("Hello World!");
});

+ // createして/へリダイレクトする
+ app.get("/create", (req, res) => {
+   await models.Post.create({
+     title: "sample title",
+     body: "sample body",
+   });
+   res.redirect("/");
+ });

app.listen(9000, () => {
  console.log("The app listening on port 5000");
});

modelsをrequireするとき、./models/だと失敗するので注意。

アクセス

Chrome
localhost:5000/create

pgAdminで確認

Servers/postgres/Databases/development/Schemas/public/Tables/の中の先ほど生成したモデル名のテーブルを右クリックし、View/Edit Data->All Rows

Screen Shot 2021-01-23 at 14.56.26.png

Data OutputタブでデータがCreateされているのが確認できる。

Read

server.js
const express = require("express");
const app = express();

const models = require("./models/index");

// 全て取得して返す
app.get("/", (req, res) => {
+   models.Post.findAll().then((posts) => {
+     res.send(posts);
+   });
-   //   res.send("Hello World!");
});

// createして/へリダイレクトする
app.get("/create", (req, res) => {
  models.Post.create({
    title: "sample title",
    body: "sample body",
  });
  res.redirect("/");
});

app.listen(9000, () => {
  console.log("The app listening on port 5000");
});

アクセス

Chrome
localhost:5000

Screen Shot 2021-01-23 at 15.00.58.png

無事に取得できているのが確認できた!

おしまい

あとはejsを入れたりしてviewを整形したり、bodyParserでFormデータを受け取ったりすればサービスとして本格的な開発が進められる。

今回のコード

https://github.com/nullworkbench/docker-express-postgresql-sequelize

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

Alexa SmartHomeSkillの開発 公式チュートリアルに加え、アカウントリンクの実装、ModeControllerの追加

はじめに

AlexaのSmartHomeSkillは、照明や扇風機などの家電を、Alexaを使って操作するための機能です。PowerOnOffやModeControllerなどのあらかじめ決められたインターフェイスがあり、それらを利用して機能を組み立てていきます。
SmartHomeSkillでは、音声認識から意図解釈まではAmazon側でビルドされたモデルによって実行するため、開発者が意図解釈のために対話エージェントを訓練する必要はありません。
CustomSkillでは、意図解釈は、開発者が、Dialogflowのようなエージェントを訓練する必要があります。

目指す姿

スマートフォンのAlexaアプリから、SmartHomeSkillを呼び出します。
SmartHomeSkillを使って、PowerOnOffおよび、ModeControllerを使えるようにします。
本記事では、Alexaを通じて、洗濯機のオン、オフ、および、洗濯モードをデリケートモードに設定するのを目指します。
基本的には、公式のチュートリアルに従って進めます。
https://developer.amazon.com/ja-JP/docs/alexa/smarthome/steps-to-build-a-smart-home-skill.html
チュートリアルに出てくるlambdaのNode.jsのコードにアカウントリンクとModeControllerのインターフェイスを追加する形で説明します。
分かりにくいところを補完しながら進めます。

アカウントリンクの実装

まずは、アカウントリンク部分の実装を説明します。
アカウントリンクのため、本記事では、Login with Amazon(LWA)を使います。
セキュリティプロファイルを作成すると、クライアントIDとクライアントシークレットが発行されます。
lwa.png.jpg

バックエンドでは、下記の通り、Authorizationのハンドリングのための関数を追加します。
下記のコードのOAUTH_CLIENT_IDに上記のLWAのクライアントID、OAUTH_CLIENT_SECRETに、クライアントシークレットを設定します。
下記の関数は、以下の記事からコピペしました(https://qiita.com/poruruba/items/5e31b82bfbdeef20519d)。

node.js
function handleAuthorization(request, context) {
        console.log("handleAuthorization function was called.");
        var code = request.directive.payload.grant.code;

        var options = {
            url: 'https://api.amazon.com/auth/o2/token',
            method: 'POST',
            headers: { 'Content-Type':'application/json' },
            json: {
                grant_type: 'authorization_code',
                code: code,
                client_id: OAUTH_CLIENT_ID,
                client_secret: OAUTH_CLIENT_SECRET
            }
        };

        return new Promise((resolve, reject) => {
            Request(options, function(error, response, body) {
                if(error){
                    console.log('error: ', error);
                    return reject(error);
                }

                console.log(body);
                accessToken = body.accessToken;

                var header = JSON.parse(JSON.stringify(request.directive.header));
                header.name = 'AcceptGrant.Response';
                context.succeed({event: {header: header, payload:{}}});
                return resolve();
            });
        });
    }

Lambda関数の冒頭の、リクエストの種類によって処理を振り分けている箇所にて、アカウントリンクを実施する場合、上記の関数を呼び出す処理を追加します。

node.js
else if (request.directive.header.namespace == 'Alexa.Authorization') {
        log("DEBUG:", "Authorization request", JSON.stringify(request));
        handleAuthorization(request, context);
    }

ここまでできた時点で、スマホのAlexaアプリから、スマートホームスキルのアカウントリンクができるようになっているはずです。

ModeControllerの追加

ModeControllerインターフェイス
https://developer.amazon.com/ja-JP/docs/alexa/device-apis/alexa-modecontroller.html
handleDiscovery関数内にて、デバイスのCapabilityを定義している箇所があります。ここに、ModeControllerのインターフェイスを追加します。

node.js
{
                          "type": "AlexaInterface",
                          "interface": "Alexa.ModeController",
                          "instance": "Washer.Mode",
                          "version": "3",
                          "properties": {
                            "supported": [
                              {
                                "name": "mode"
                              }
                            ],
                            "retrievable": true,
                            "proactivelyReported": true,
                            "nonControllable": false
                          },
                          "capabilityResources": {
                            "friendlyNames": [
                              {
                                "@type": "text",
                                "value": {
                                  "text": "モード",
                                  "locale": "ja-JP"
                                }
                              }
                            ]
                          },
                          "configuration": {
                            "ordered": false,
                            "supportedModes": [
                              {
                                "value": "Mode.Delicate",
                                "modeResources": {
                                  "friendlyNames": [
                                    {
                                      "@type": "text",
                                      "value": {
                                        "text": "デリケート",
                                        "locale": "ja-JP"
                                      }
                                    }
                                  ]
                                }
                              }
                            ]
                          }
                        } // end of modecontroller interface

これで、アカウントリンク済みの状態で、「デリケートモードにして」と発話すると、Alexa側としては、デリケートモードがデバイスに存在していることは認識してくれます。
ただ、デリケートモードにしてくれと言われた時の実際の動作が規定されていないので、そこを決めてやります。
以下のデモコードを追加します。

node.js
function handleModeControl(request, context) {
        log("DEBUG handleModeControl called.");
        // 検出中に渡されたデバイスIDを取得します
        var requestMethod = request.directive.header.name;
        var instance = request.directive.header.instance;
        var responseHeader = request.directive.header;
        responseHeader.namespace = "Alexa";
        responseHeader.name = "Response";
        responseHeader.messageId = responseHeader.messageId + "-R";
        // リクエスト中のユーザートークンパスを取得します
        var requestToken = request.directive.endpoint.scope.token;
        var modeResult = request.directive.payload.mode;

        // 本来は、ここで洗濯機の制御APIを呼び出します

        var contextResult = {
            "properties": [{
                "namespace": "Alexa.ModeController",
                "instance": instance,
                "name": "mode",
                "value": modeResult,
                "timeOfSample": "2017-09-03T16:20:50.52Z", //結果から取得します。
                "uncertaintyInMilliseconds": 50
            }]
        };
        var response = {
            context: contextResult,
            event: {
                header: responseHeader,
                endpoint: {
                    scope: {
                        type: "BearerToken",
                        token: requestToken
                    },
                    endpointId: "demo_id"
                },
                payload: {}
            }
        };
        log("DEBUG Alexa.PowerController ", JSON.stringify(response));
        context.succeed(response);
    }

さらに、Lambdaの冒頭の処理分岐部分に以下を追記します。

node.js
else if (request.directive.header.namespace == 'Alexa.ModeController'){
      log("DEBUG:", JSON.stringify(request));
      handleModeControl(request, context);
    }

これで、スマホアプリからデリケートモードにして、と発話すると、handleModeControl()が呼ばれるようになります。
ほぼ同様にして、他のインターフェイスも実装することができます。

おわりに

AlexaのSmartHomeSkillを使い、電源オンオフとモード切り替えを実装しました。
SmartHomeSkillを使うことで、簡単にIoT家電を操作できるようになります。
これが足りないぞというのがありましたら、コメントお願いします。

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

Gatsbyでreact-modalを使う方法について

最近Gatsbyを使って勉強をしてみています。

Reactを素で使うよりもとっかかりやすく、StarterやPluginの充実、GraphQLの標準装備と個人的には学習コストが少ないし、Reactを最初にやるには良いんじゃないかなとは思ってます。

Gatsby

今回は、Gatsbyを使ってページにモーダルウィンドウの機能を実装するために使えるreact-modalをGatsbyで使う場合に結構ハマってしまったので、備忘録を兼ねてメモです。

react-modal documentation

react-modalのセットアップ

react-modalの初期設定は簡単です。
npmなりyarnなどを使ってreact-modalをインストールすればOKです。

command
npm install react-modal

react-modalを使う(1ページに1つのモーダルのみを表示する場合)

まずはシンプルに1ページに1つのモーダルだけを表示する場合です。
例としてmodal-simple.jsというページで設定したとすると以下のようなコードになります。

page/modal-simple.js
import React from "react"
import Modal from "react-modal";

// react-modalの初期設定
// root nodeのSelectorを設定
Modal.setAppElement("#___gatsby");
// react-modalのデザインを設定修正する場合の例
Modal.defaultStyles.overlay.backgroundColor = "black";

const ModalSingle = () => {

  //モーダルの表示状態と切り替える為にState(props)を準備
  const [modalIsOpen, setIsOpen] = React.useState(false);

  // shouldCloseOnEscやshouldCloseOnOverlayCloseを使う場合に設定が必要
  const handleCloseModal = () => {
    setIsOpen(false)
  }

  return(
    <div>
      <div className="container">
        ここにモーダルを表示します
        <button className="button" onClick={() => setIsOpen(true)}>ボタン</button>
      </div>
      <Modal 
        isOpen={modalIsOpen}
        onRequestClose={() => handleCloseModal()}
        shouldCloseOnEsc={true}
        shouldCloseOnOverlayClick={true}
      >
        <div>モーダルの表示内容です</div>
      </Modal>
    </div>
  )
}

export default ModalSingle

これで以下のようなモーダル表示をするためのボタンと、モーダル表示がされるはずです。
イメージ画像

ポイントはGatsbyでは明確にprops等が通常見えないので…
React.useState()を使ってステータス管理用のpropsを準備してます。

また、shouldCloseOnEscなどを使いたい場合は、onRequestCloseの定義も必要です。
そこを設定しないと機能しないので気をつけてください。
onRequestClose - react-modal documentation

応用編:1ページに複数モーダルを表示したい場合

上の設定だと1ページに1つのモーダルなら良いのですが、複数だとうまく出来ません。
複数のモーダルの表示を切り替え分けたい場合は、ステータス管理を少し調整するコツが必要です。

modal-multiple.js
import React from "react"
import Modal from "react-modal";

// react-modalの初期設定
// root nodeのSelectorを設定
Modal.setAppElement("#___gatsby");

const ModalMultiple = () => {

  // モーダルの表示状態と切り替える為にState(props)を準備
  // false = 非表示、数値 = 表示しているModalの番目とする
  const [modalIsOpen, setIsOpen] = React.useState(false);

  //どのモーダルを表示するのか操作するために関数を準備
  const handleOpenModal = (num) => {
    setIsOpen(num) 
  }

  // shouldCloseOnEscやshouldCloseOnOverlayCliceを使う場合に設定が必要
  // モーダルを非表示の状態にするため、falseを指定する
  const handleCloseModal = () => {
    setIsOpen(false)
  }

  return(
    <div>
      <div className="container">
        パターン2ここにモーダルを表示します
        <ul>
          <li><button className="button" onClick={() => handleOpenModal(0)}>モーダル1</button></li>
        </ul>
        <ul>
          <li><button className="button" onClick={() => handleOpenModal(1)}>モーダル2</button></li>
        </ul>
        <ul>
          <li><button className="button" onClick={() => handleOpenModal(2)}>モーダル3</button></li>
        </ul>
        <ul>
          <li><button className="button" onClick={() => handleOpenModal(3)}>モーダル4</button></li>
        </ul>
      </div>
      <Modal 
        isOpen={(modalIsOpen === 0)}
        onRequestClose={() => handleCloseModal()}
        shouldCloseOnEsc={true}
        shouldCloseOnOverlayClick={true}
      >
        <div>モーダル1の表示内容です</div>
      </Modal>
      <Modal 
        isOpen={(modalIsOpen === 1)}
        onRequestClose={() => handleCloseModal()}
        shouldCloseOnEsc={true}
        shouldCloseOnOverlayClick={true}
      >
        <div>モーダル2の表示内容です</div>
      </Modal>
      <Modal 
        isOpen={(modalIsOpen === 2)}
        onRequestClose={() => handleCloseModal()}
        shouldCloseOnEsc={true}
        shouldCloseOnOverlayClick={true}
      >
        <div>モーダル3の表示内容です</div>
      </Modal>
      <Modal 
        isOpen={(modalIsOpen === 3)}
        onRequestClose={() => handleCloseModal()}
        shouldCloseOnEsc={true}
        shouldCloseOnOverlayClick={true}
      >
        <div>モーダル4の表示内容です</div>
      </Modal>

    </div>
  )
}

export default ModalMultiple

で定義するisOpenの状態を数値にしてしまえば、その値が合致した時だけ表示されます。
各Modalの表示条件をModalIsOpenの値で条件て設定し、表示制御する関数(今回はhandleOpenModal())を使って制御をすれば動きます。

image.gif

今回はOnClick要素でOpenしたいモーダルのID(0−3)を指定すれば開くように設定しています。

実際に今回動作を組んでみたサンプルコードをgithubにあげてますので、参考までに。

manji6/gatsby-modal: Gatsby で react-modalを使うサンプル

これがモーダル複数バージョンのサンプルコード
gatsby-modal/modal-multiple.js at main · manji6/gatsby-modal

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

【メモ】LambdaからgoogleDriveAPIを操作する

はじめに

LambdaからgoogleAPIでGooglDriveをいじりたかったのですが、サービスアカウントを用いた参考文献が少なく、理解するのに時間がかかったのでメモ感覚で書いておきます。

本題

Googleデベロッパーコンソールに行く,プロジェクトを作成

Googleデッッベロッパーコンソールにログイン
スクリーンショット 2021-01-23 0.32.38.png

APIの有効化>APIライブラリからGoogle Drive APIを有効化させる

スクリーンショット 2021-01-23 0.33.27.png

認証情報の追加>サービスアカウント

スクリーンショット 2021-01-23 0.34.53.png

ひとまずロールにオーナーをつけてみよう

スクリーンショット 2021-01-23 0.36.29.png

アカウントが作成されたら>JSONの鍵を生成&ダウンロード

スクリーンショット 2021-01-23 0.38.03.png
※鍵のタイプを聞かれたらひとまずJSONを選択
↓こんな感じのやつ

{
  "type": "service_account",
  "project_id": "exxxxxxxxxxxxxxxxxxxx",
  "private_key_id": "axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "private_key": "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n",
  "client_email": "id-xxx@xxxxxxxxxxxxxxx.iam.gserviceaccount.com",
  "client_id": "xxxxxxxxxxxxxxxxxx",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxxxxxxxxxxxxxxxxxxx5.iam.gserviceaccount.co

ソースコードの作成

cd desktop
mkdir sample
cd sample
touch index.js
touch privatekey.json
npm init -y

privatekey.jsonに先ほどインストールした認証情報のJSONを入れる

//googleAPIをいじるためのモジュール
npm install googleapis
index.js
const { google } = require("googleapis");
const privatekey = require("./privatekey.json");
exports.handler = async (event) => {
  const res = await Promise.resolve()
    .then(function () {
      return new Promise(function (resolve, reject) {
        //JWT auth clientの設定
        const jwtClient = new google.auth.JWT(
          privatekey.client_email,
          null,
          privatekey.private_key,
          ["https://www.googleapis.com/auth/drive"]
        );
        //authenticate request
        jwtClient.authorize(function (err, tokens) {
          if (err) {
            reject(err);
          } else {
            //認証成功
            resolve(jwtClient);
          }
        });
      });
    })
    .then(function (jwtClient) {
      return new Promise(async function (resolve, reject) {
        const drive = google.drive({ version: "v3", auth: jwtClient });
        drive.files.list(
          {
            pageSize: 10,
            fields: "nextPageToken, files(id, name)",
          },
          (err, res) => {
            if (err) reject(err);
            const files = res.data.files;
            if (files.length) {
              resolve(files);
            } else {
              resolve("No files found.");
            }
          }
        );
      });
    });
    console.log(res)
};



ややこしかったこと

認証方法にはいくつかあり、 公式リファレンスのquickstartにもあるようなOAuth 2.0 クライアント IDを用いた方法では、トークンの有効期限が一時間のため、常時使い続けるためにはリフレッシュトークンを用いて一時間に一回アクセストークンを更新しなければならない。こちらの場合はGUIで試すこともできる。

そのため、今回はサービスアカウントを利用した。
サービスアカウントでは、スコープを与えるだけでは、ファイルに書き込んだりすることができない。googleDriveのコンソールからサービスアカウントのメールアドレスに編集権限を与える必要がある。

お世話になったサイト

https://qiita.com/Fujimon_fn/items/9a0ec4eca75ce0784722
https://developers.google.com/drive/api/v3/quickstart/nodejs
https://developers.google.com/oauthplayground/
https://playwithgoogleapi.hatenablog.com/entry/2019/06/30/133415
https://qiita.com/n0bisuke/items/ff1479cd14e7a0c0be0c

まだ追い切れてなくて気になったやつ

https://github.com/googleapis/google-auth-library-nodejs#samples

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

json-serverを使って簡単にAPIをmockする

はじめに :sleepy:

JSON Server

https://github.com/typicode/json-server

  • 簡単なものならコーディング不要でサクッとAPIをモックできるNode.jsのライブラリ
  • 詳しく使い方を知りたい場合は上記リポジトリのREADMEを読んだほうが良い

まえがき

まだAPIが用意できていないけど、フロント側の実装を進めたいときに便利です。
本記事ではサクッと使える簡単なモックと、応用として以下2つの方法を記載したいと思います。

  1. POSTリクエストで任意のレスポンスを返す
  2. サーバーサイドエラーのモックする

サクッと使う

任意のディレクトリを作成し移動後、 json-server をインストールする

$ npm install json-server --save-dev

db.json を作成し、モックしたいデータを挿入

db.json
{
  "molcars": [
    {
      "name": "ポテト",
      "favoriteFood": "ニンジン"
    },
    {
      "name": "シロモ",
      "favoriteFood": "レタス"
    }
  ],
  "episodes": [
    {
      "id": 1,
      "title": "渋滞は誰のせい?"
    },
    {
      "id": 2,
      "title": "銀行強盗をつかまえろ!"
    }
  ]
}

これでおわり!
あとは以下コマンドを叩いて json-server を起動し……

$ json-server --watch db.json

curlなりなんなりで、このように db.json に記載したデータが返却されます。

$ curl -X GET 'http://localhost:3000/molcars'
[
  {
    "name": "ポテト",
    "favoriteFood": "ニンジン"
  },
  {
    "name": "シロモ",
    "favoriteFood": "レタス"
  }
]
$ curl -X GET 'http://localhost:3000/episodes'
[
  {
    "id": 1,
    "title": "渋滞は誰のせい?"
  },
  {
    "id": 2,
    "title": "銀行強盗をつかまえろ!"
  }
]

応用

上記だけじゃ機能として物足りない場合、
json-server をモジュールとして使用することでほしい機能を追加できます。

json-server をモジュールとして使う

公式のREADMEにあるシンプルなコードです。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)
server.use(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})

以下コマンドで起動可能

$ node server.js

この時点でできることはサクッと編と基本的に同じで、
上記コマンドで起動したあとにcurlするとdb.jsonの値が同じように返却されます。

というわけで、上記のコードにほしい機能を追加していきましょう。

POSTリクエストで任意のレスポンスを返す

サクッと編で作成したモックでは、
POSTでリクエストを送ると実際に以下のようなレスポンスが返却され、
なおかつこのレスポンスと同じデータが db.json に勝手に挿入されます。

{  
  "id":2
}

(idの値は自動でインクリメントされます。)

なのでPOSTでリクエストした際のレスポンスを設定したい場合や、
勝手に db.json に値を追加してほしくない場合は
POSTリクエストを server.js 内でGETとして処理するように変更します。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)

/** ↓ POST を GET として処理する場合はここを追加 ↓ */
server.use(jsonServer.bodyParser) // POST, PUT, PATCH を扱うために必要
server.use((req, res, next) => {
  if (req.method === 'POST') {
    // POST のリクエストを GET に変換
    req.method = 'GET'
  }
  next()
})
/** ↑ POST を GET として処理する場合はここを追加 ↑ */

server.use(router)

server.listen(3000, () => {
  console.log('JSON Server is running')
})

あとは先程と同様に起動して

$ node server.js

curlでレスポンスが返ってくることが確認できます。

$ curl -X POST 'http://localhost:3000/molcars'
[
  {
    "name": "ポテト",
    "favoriteFood": "ニンジン"
  },
  {
    "name": "シロモ",
    "favoriteFood": "レタス"
  }
]

サーバーサイドエラーのモック方法

公式のREADMEに記載されていますが、
レスポンスを変更したい場合は router.render メソッドを上書きします。

任意のステータスコードおよびエラーメッセージを返す場合は
server.js を以下のように変更します。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)

server.use(router)

server.listen(3000, () => {
  console.log('JSON Server is running')
})

/** ↓ サーバサイドエラーをモックしたい場合はここを追加 ↓ */
router.render = (req, res) => {
  // ステータスコードを設定
  res.status(500).jsonp({
    error: "PUI PUI"
  })
}
/** ↑ サーバサイドエラーをモックしたい場合はここを追加 ↑ */

これも同様に起動すれば

$ node server.js

叩いた際に ステータスコード500
エラーメッセージ「"error": "PUI PUI"」 が返却されるようになります。

$ curl -X GET 'http://localhost:3000/molcars'
{
  "error": "PUI PUI"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

json-serverでREST APIをmockする

はじめに :sleepy:

JSON Server

https://github.com/typicode/json-server

  • 簡単なものならコーディング不要でサクッとAPIをモックできるNode.jsのライブラリ
  • 詳しく使い方を知りたい場合は上記リポジトリのREADMEを読んだほうが良い

まえがき

まだAPIが用意できていないけど、フロント側の実装を進めたいときに便利です。
本記事ではサクッと使える簡単なモックと、応用として以下2つの方法を記載したいと思います。

  1. POSTリクエストで任意のレスポンスを返す
  2. サーバーサイドエラーのモックする

サクッと使う

任意のディレクトリを作成し移動後、 json-server をインストールする

$ npm install json-server --save-dev

db.json を作成し、モックしたいデータを挿入

db.json
{
  "molcars": [
    {
      "name": "ポテト",
      "favoriteFood": "ニンジン"
    },
    {
      "name": "シロモ",
      "favoriteFood": "レタス"
    }
  ],
  "episodes": [
    {
      "id": 1,
      "title": "渋滞は誰のせい?"
    },
    {
      "id": 2,
      "title": "銀行強盗をつかまえろ!"
    }
  ]
}

これでおわり!
あとは以下コマンドを叩いて json-server を起動し……

$ json-server --watch db.json

curlなりなんなりで、このように db.json に記載したデータが返却されます。

$ curl -X GET 'http://localhost:3000/molcars'
[
  {
    "name": "ポテト",
    "favoriteFood": "ニンジン"
  },
  {
    "name": "シロモ",
    "favoriteFood": "レタス"
  }
]
$ curl -X GET 'http://localhost:3000/episodes'
[
  {
    "id": 1,
    "title": "渋滞は誰のせい?"
  },
  {
    "id": 2,
    "title": "銀行強盗をつかまえろ!"
  }
]

応用

上記だけじゃ機能として物足りない場合、
json-server をモジュールとして使用することでほしい機能を追加できます。

json-server をモジュールとして使う

公式のREADMEにあるシンプルなコードです。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)
server.use(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})

以下コマンドで起動可能

$ node server.js

この時点でできることはサクッと編と基本的に同じで、
上記コマンドで起動したあとにcurlするとdb.jsonの値が同じように返却されます。

というわけで、上記のコードにほしい機能を追加していきましょう。

POSTリクエストで任意のレスポンスを返す

サクッと編で作成したモックでは、
POSTでリクエストを送ると実際に以下のようなレスポンスが返却され、
なおかつこのレスポンスと同じデータが db.json に勝手に挿入されます。

{  
  "id":2
}

(idの値は自動でインクリメントされます。)

なのでPOSTでリクエストした際のレスポンスを設定したい場合や、
勝手に db.json に値を追加してほしくない場合は
POSTリクエストを server.js 内でGETとして処理するように変更します。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)

/** ↓ POST を GET として処理する場合はここを追加 ↓ */
server.use(jsonServer.bodyParser) // POST, PUT, PATCH を扱うために必要
server.use((req, res, next) => {
  if (req.method === 'POST') {
    // POST のリクエストを GET に変換
    req.method = 'GET'
  }
  next()
})
/** ↑ POST を GET として処理する場合はここを追加 ↑ */

server.use(router)

server.listen(3000, () => {
  console.log('JSON Server is running')
})

あとは先程と同様に起動して

$ node server.js

curlでレスポンスが返ってくることが確認できます。

$ curl -X POST 'http://localhost:3000/molcars'
[
  {
    "name": "ポテト",
    "favoriteFood": "ニンジン"
  },
  {
    "name": "シロモ",
    "favoriteFood": "レタス"
  }
]

サーバーサイドエラーのモック方法

公式のREADMEに記載されていますが、
レスポンスを変更したい場合は router.render メソッドを上書きします。

任意のステータスコードおよびエラーメッセージを返す場合は
server.js を以下のように変更します。

server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)

server.use(router)

server.listen(3000, () => {
  console.log('JSON Server is running')
})

/** ↓ サーバサイドエラーをモックしたい場合はここを追加 ↓ */
router.render = (req, res) => {
  // ステータスコードを設定
  res.status(500).jsonp({
    error: "PUI PUI"
  })
}
/** ↑ サーバサイドエラーをモックしたい場合はここを追加 ↑ */

これも同様に起動すれば

$ node server.js

叩いた際に ステータスコード500
エラーメッセージ「"error": "PUI PUI"」 が返却されるようになります。

$ curl -X GET 'http://localhost:3000/molcars'
{
  "error": "PUI PUI"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む