1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Node.jsとFirebaseを使ってRedmineチケットの更新を通知してくれるSlack Appのバックエンドメモ

Last updated at Posted at 2020-05-25

通知イメージ

スクリーンショット 2020-05-24 17.48.35.png

root

package.json

{
  "name": "redticket",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "serve": "firebase emulators:start",
    "deploy": "firebase deploy",
    "logs": "firebase functions:log"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-eslint": "^10.1.0",
    "eslint": "^7.0.0",
    "eslint-plugin-promise": "^4.2.1",
    "firebase-tools": "^8.2.0",
    "prettier": "^2.0.5"
  }
}

firebase.json

{
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "api"
      }
    ]
  }
}

.prettierrc

{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "printWidth": 100,
  "arrowParens": "avoid"
}

.eslintrc

{
  "extends": ["eslint:recommended"],
  "plugins": [],
  "parser": "babel-eslint",
  "parserOptions": {},
  "env": {
    "es6": true,
    "node": true
  },
  "globals": {},
  "rules": {
    "semi": ["error", "never"],
    "arrow-parens": "off"
  }
}

functions

package,json

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {},
  "engines": {
    "node": "8"
  },
  "dependencies": {
    "@slack/web-api": "^5.8.1",
    "axios": "^0.19.2",
    "express": "^4.17.1",
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1"
  },
  "devDependencies": {
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

index.js

const admin = require('firebase-admin')
const axios = require('axios')
const serviceAccount = require('./serviceAccount.json') // Firestoreの秘密鍵
const Express = require('express')
const functions = require('firebase-functions')
const { WebClient } = require('@slack/web-api')
const client = new WebClient('[Bot User OAuth Access Token]')

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://xxxxxxx-xxxxx.firebaseio.com',
})
const usersCollection = admin.firestore().collection('users')
const redmineBaseURL = 'http://redmine.url'

const api = Express()
api.use(Express.json())
api.use(Express.urlencoded({ extended: true }))

api.post('/redmine', async (req, res) => {
  try {
    const ticketUrl = req.body.payload.url
    const ticket = req.body.payload.issue
    const comment = req.body.payload.journal
    const watchers = [...ticket.watchers]
    const query = usersCollection.where(
      'redmine_mail',
      'in',
      watchers.map(watcher => watcher.mail)
    )
    query.get().then(querySnapshot => {
      if (!querySnapshot.empty) {
        querySnapshot.docs.forEach(doc => {
          client.chat.postMessage({
            channel: doc.data().channel_id,
            text: ``,
            blocks: [
              {
                type: 'section',
                text: {
                  type: 'mrkdwn',
                  text: `*Subject:*\n${ticket.subject}\n\n*URL:*\n${ticketUrl}\n\n*Description:*\n${ticket.description}`,
                },
              },
              {
                type: 'divider',
              },
              {
                type: 'section',
                text: {
                  type: 'mrkdwn',
                  text: `*Latest comment:*\n${comment.notes}\n\n*Comment by:*\n${comment.author.firstname} ${comment.author.lastname}`,
                },
              },
            ],
          })
        })
      }
    })
  } catch (err) {
    console.error(err)
  } finally {
    res.status(200).send()
  }
})

api.post('/redticket', async (req, res) => {
  try {
    res.send('') // Error avoid

    await client.chat.postMessage({
      channel: req.body.channel_id,
      text: `🏃: Start /redticket ${req.body.text}`,
    })
    const mode = req.body.text.trim() !== '' ? req.body.text.trim().split(' ')[0] : ''
    switch (mode) {
      case '':
      case 'help': {
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: '',
          blocks: [
            {
              type: 'section',
              text: {
                type: 'mrkdwn',
                text:
                  "Hello👋 I will explain how to use RedTicket. The first step is to initialize the user data. The command is `/redticket init` 💻\n\nThe next step is to register your redmine account! You will need redmine's API token to register your account. The command is `/redticket verify [API Token]`. Let's sign up for an account right away 🏃 You can check your registered user data at any time at `/redticket check` 👌\n\nIf you need help, try send the `/redticket` or `/redticket help` 📖",
              },
            },
            {
              type: 'divider',
            },
          ],
        })
        break
      }

      case 'init': {
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Checking the existence of the user data`,
        })
        const userInfo = await usersCollection.doc(`${req.body.team_id}${req.body.user_id}`).get()
        const setDocument = async () => {
          const data = {
            team_id: req.body.team_id,
            team_domain: req.body.team_domain,
            user_id: req.body.user_id,
            channel_id: req.body.channel_id,
            user_name: req.body.user_name,
          }
          await client.chat.postMessage({
            channel: req.body.channel_id,
            text: `🏃: Creating your user data.`,
          })
          return await usersCollection
            .doc(`${req.body.team_id}${req.body.user_id}`)
            .set(data)
            .then(async () => {
              const userInfo = await usersCollection
                .doc(`${req.body.team_id}${req.body.user_id}`)
                .get()
              return userInfo.data()
            })
        }
        const docRef = userInfo.data() === undefined ? await setDocument() : userInfo.data()
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Your user data.\n\n${JSON.stringify(docRef, null, '\t')}`,
        })
        break
      }

      case 'check': {
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Reading your user data`,
        })
        const userInfo = await usersCollection.doc(`${req.body.team_id}${req.body.user_id}`).get()
        const docRef = userInfo.data()
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Your user data.\n\n${JSON.stringify(docRef, null, '\t')}`,
        })
        break
      }

      case 'verify': {
        if (req.body.text.trim().split(' ').length <= 1) {
          await client.chat.postMessage({
            channel: req.body.channel_id,
            text: `🙅: Need Redmine API Token.`,
          })
          break
        }
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Verify the Redmine API Token.`,
        })

        const redmineUserInfo = await axios.get(`${redmineBaseURL}/my/account.json`, {
          params: {
            key: req.body.text.trim().split(' ')[1],
          },
        })

        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Your user data in Redmine.\n\n${JSON.stringify(
            redmineUserInfo.data.user,
            null,
            '\t'
          )}`,
        })

        const userInfo = await usersCollection.doc(`${req.body.team_id}${req.body.user_id}`).get()
        const docRef = userInfo.data()
        if (!userInfo.exists) {
          throw new Error('No such your data')
        }
        docRef.redmine_login = redmineUserInfo.data.user.login
        docRef.redmine_mail = redmineUserInfo.data.user.mail

        const updatedUserData = await usersCollection
          .doc(`${req.body.team_id}${req.body.user_id}`)
          .update(docRef)
          .then(async () => {
            const userInfo = await usersCollection
              .doc(`${req.body.team_id}${req.body.user_id}`)
              .get()
            return userInfo.data()
          })

        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Your user data.\n\n${JSON.stringify(updatedUserData, null, '\t')}`,
        })
        break
      }

      case 'project': {
        await client.chat.postMessage({
          channel: req.body.channel_id,
          text: `🏃: Verify the Redmine API Token.`,
        })

        break
      }

      default:
        throw new Error('Invalid parameters.')
    }
  } catch (error) {
    await client.chat.postMessage({ channel: req.body.channel_id, text: `🙅: ${error}` })
  } finally {
    await client.chat.postMessage({
      channel: req.body.channel_id,
      text: `🎉: Done /redticket ${req.body.text}`,
    })
  }
})

exports.api = functions.https.onRequest(api)

リファクタしないと。

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?