Nuxt.jsとExpress.jsを使ってリマインダー機能をつくってみた

# Node製リマインダー機能

予約した内容を前日にリマインドするという機能をnodemailerでできないかと考えていたら、node schedulerを使うとできるみたいなことが書いてあったので作ってみた。  
今回はVueファイル側でやることはないので、Expressを基本いじる感じになる。

## 環境

- Nuxt.js v2.4.5
- backpack-core
- Express v4.16.4
- nodemailer v5.1.1
- nodemailer-smtp-transport v2.7.4
- node-schedule v1.3.2
- moment v2.24.0
- mysql v2.16.0
  
## インストール

対象のプロジェクト下で

```bash
yarn add nodemailer nodemailer-smtp-transport node-schedule moment mysql backpack-core
```

## backpack-coreを設定

ES6を使えるようにする。
<https://qiita.com/sauzar18/items/32a8553587e2bdcf3689>  
ここら辺見て設定してください。

## SMTPの設定

```js:server/config.js
const config = {
  transporter: {
    service: 'example.jp',
    port: 465, // ssl対応port
    auth: {
      user: 'yoyaku@example.jp',
      pass: 'passwordnyuryokusitekure'
    },
    use_authentication: true,
    tls: {
      rejectUnauthorized: false
    }
  }
}

export default config
```

ここを対象のサーバーの情報を入力してください

## データベースをコネクト

今回はmysqlを使って行う。

```js:server/mysql.js
import mysql from 'mysql'

const dbConfig = {
  host: 'localhost',
  user: 'example',
  database: 'reminder_db',
  password: 'examplepass',
  multipleStatements: true
}
const connection = mysql.createConnection(dbConfig)

export default connection
```

## リマインドの設定

```js:server/api/reminder.js
import { Router } from 'express'
import consola from 'consola'
import nodemailer from 'nodemailer'
import schedule from 'node-schedule'
import moment from 'moment'
import connection from '../mysql'
const config = require('../config').default
const router = Router()
// eslint-disable-next-line no-unused-vars
const job = schedule.scheduleJob({
  hour: 18,
  minute: 30
}, function () {
  const date = moment().add(1, 'days').format('YYYY-MM-DD')
  const emailQuery = `SELECT * FROM table WHERE reserve_date = "${date}" AND post_type NOT IN ('cancel')`
  connection.query(emailQuery, function (err, rows) {
    if (err) {
      consola.ready(err)
    } else {
      const transporter = nodemailer.createTransport(config.transporter)
      for (let i = 0; i < rows.length; i++) {
        const json = JSON.stringify(rows[i])
        const user = JSON.parse(json)
        const output = `予約システム

${user.username}様
予約前日になりました。
予約内容を確認の上、ご来店ください。

予約店：${user.company_name}
予約予定日時：${moment(user.start).format('YYYY年MM月DD日 h時mm日')}
予約内容：${user.title}

また、当日キャンセルにおいてはシステム上で行っても
受理されない可能性がありますので、店舗へご連絡の上ご確認ください。
`
        consola.ready(output)
        const mailOptions = {
          from: 'example.email.com',
          to: user.email,
          subject: '予約前日のお知らせ',
          text: output
        }
        transporter.sendMail(mailOptions, (error, info) => {
          if (error) {
            // eslint-disable-next-line no-undef
            consola.ready(error)
          } else {
            // eslint-disable-next-line no-undef
            consola.ready('success')
          }
        })
      }
    }
  })
})

export default router
```

これをapi読み込み

```js:server/api/index.js
import { Router } from 'express'

import reminde from './reminder'

const router = Router()

router.use(reminde)

export default router
```

さらにapiも読み込んであげる

```js:server/index.js
import api from './api'

app.use('/api', api)
```

今回DBから取得しているデータはこんな感じ

```json
[
    { id: 0, company_name: '店舗1', reserve_date: '2019-03-16', start: '2019-03-16 07:00:00', end: '2019-03-16 07:30:00', title:'予約内容1', username: '田中太郎', email: 'example@example.com', post_type: 'new' },
    { id: 1, company_name: '店舗2', reserve_date: '2019-03-16', start: '2019-03-16 08:00:00', end: '2019-03-16 08:30:00', title:'予約内容2', username: '田中次郎', email: 'example2@example.com', post_type: 'cancel' },
    { id: 2, company_name: '店舗3', reserve_date: '2019-03-16', start: '2019-03-16 09:00:00', end: '2019-03-16 09:30:00', title:'予約内容3', username: '田中次郎', email: 'example3@example.com', post_type: 'new' }
]
```

やっていること

### node schedulerでリマインドする時間を設定

```js
const job = schedule.scheduleJob({
  hour: 18,
  minute: 30
}, function () {
    // script
})
```

毎日18時30分にスクリプトを実行する。

### データベースから送信する情報を取得

予約日の一日前を対象としたデータを取得したいので、  
moment.jsをつかって今日より一日追加してreserve_dateと同じ型に変換しWHEREで検索。  

また、キャンセルしたユーザーを除外して取得。

```js
const date = moment().add(1, 'days').format('YYYY-MM-DD')
const emailQuery = `SELECT * FROM table WHERE reserve_date = "${date}" AND post_type NOT IN ('cancel')`
```

### mysqlにアクセスしてSELECTした内容をもとにループさせる

```js
connection.query(emailQuery, function (err, rows) {
    if (err) {
        consola.ready(err)
    } else {
        const transporter = nodemailer.createTransport(config.transporter)
        for (let i = 0; i < rows.length; i++) {
        const json = JSON.stringify(rows[i])
        const user = JSON.parse(json)
        const output = `予約システム

${user.username}様
予約前日になりました。
予約内容を確認の上、ご来店ください。

予約店：${user.company_name}
予約予定日時：${moment(user.start).format('YYYY年MM月DD日 h時mm日')}
予約内容：${user.title}

また、当日キャンセルにおいてはシステム上で行っても
受理されない可能性がありますので、店舗へご連絡の上ご確認ください。
`
    consola.ready(output)
    const mailOptions = {
        from: 'example.email.com',
        to: user.email,
        subject: '予約前日のお知らせ',
        text: output
    }
    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
        // eslint-disable-next-line no-undef
        consola.ready(error)
        } else {
        // eslint-disable-next-line no-undef
        consola.ready('success')
        }
    })
    }
}
})
```

取得したデータをrowsに格納して、JSON形式に整形する。  
ループ分のなかにnodemailerの設定と文章をつくる  
各データに紐づく内容を文章に代入してあげ、対象のユーザーむけに送信をする。

これで設置が完了。

Expressサーバーが動いていれば、基本的に毎日、リマインド対象者を検索し、いれば送信してくれるようになります。

## まとめ

node schedulerけっこういろんなところで使えそうだし、リマインダー機能つくるのが結構簡単につくれた。