m's blog

備忘録とかメモとか

【Redmine】Node.jsでRedmineにカスタムAPIを追加してみる!

Node.js で Redmine にカスタム API を追加してみる

今回は Node.js を使って、Redmine にカスタム API を追加 してみたいと思います!

以下のリポジトリに設置したサンプルコードを使って、解説を進めていきたいと思います。

目次

必要な環境

本記事の内容をトレースする場合は、「 Docker 」 と 「 Docker Compose 」 が必要になります。

Redmine 環境や Node.js 環境は、サンプルコード中の docker-compose.yml を使ってセットアップするので、インストールする必要はありません。

簡単な流れ

本記事では、以下のような流れで解説を進めていきます。

  1. サンプルコードを取得
  2. サンプルコードの中身を簡単に解説
  3. 実際にカスタム API を使ってみる
  4. まとめ

それでは、早速、Node.js を使った、Redmine のカスタム API の実装について解説していきたいと思います!

サンプルコードの取得

サンプルコードを以下のリポジトリに設置しています。

以下のコマンドで、サンプルコードをクローンしてください。

git clone https://github.com/mm0202/template_custom-redmine-api-by-nodejs.git

※ サンプルコードはテンプレートリポジトリとして公開しています。テンプレートとしても使用可能ですので、自由に使ってください。

サンプルコードの解説

サンプルコードのベース

サンプルコードは、以下のコマンド ( Express ジェネレータ ) で生成したコードをベースにしています。

express --view=pug src

追加・編集ファイル

追加・編集しているファイルは以下の5つです。

  • package.json
  • .env.template
  • Dockerfile
  • docker-compose.yml
  • src/routes/index.js

package.json

package.json ファイルはルートディレクトリに移動 しています。

package.json の中身は、以下のように調整しています。

{
  "scripts": {
    "start": "node ./src/bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "pug": "2.0.0-beta11",
    "axios": "^0.19.2"
  }
}

package.json の移動にともなって、script.start フィールドを node ./src/bin/www に変更 しています。

これにより、node_modulessrc ディレクトリの外に出して、npmパッケージのインストールは、後述の Dockerfile で行うようにしています。

また、コード中で使用する axios ライブラリを dependencies に追加 しています。

.env.template

環境構築とコード中で環境変数として使用します。

.env.template の中身は以下の通り

# For node container
API_ACCESS_KEY=[APIアクセスキー]
REDMINE_API=redmine:3000

# For redmine container
REDMINE_DB_MYSQL=db
REDMINE_DB_PASSWORD=example
REDMINE_SECRET_KEY_BASE=supersecretkey

# For db container
MYSQL_ROOT_PASSWORD=example
MYSQL_DATABASE=redmine

ファイル名を .env に変更して使用します。

[APIアクセスキー]の部分は後ほど、Redmine からアクセスキーを取得して置き換えます。

Dockerfile

Node.js コンテナ用のビルドファイル です。

FROM node

WORKDIR /usr/src/app

COPY package*.json ./

RUN yarn install --production

CMD ["yarn", "start"]

npm パッケージはイメージビルド時にインストール します。

ですので、パッケージを追加する場合は、再ビルドが必要 になります。

WORKDIR/usr/src/app を指定しています。

Node.js コンテナ内でのプロジェクトルートは /usr/src/app になります。

docker-compose.yml

トライアル用に使用するコンテナの、起動設定です。

カスタム API 設置用の Node.js コンテナと、Redmine コンテナ、Redmine 用の db コンテナを起動 します。

version: "3"

services:
  node:
    build: .
    ports:
      - 53001:3000
    env_file:
      - .env
    volumes:
      - ./src:/usr/src/app/src

  redmine:
    image: redmine
    restart: always
    ports:
      - 53000:3000
    env_file:
      - .env

  db:
    image: mariadb
    restart: always
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    env_file:
      - .env

構成は以下のような感じになります。

Node.js で Redmine にカスタム API を追加してみる

Node.js コンテナには、先ほどの Dockerfile をビルドしたイメージを使用 します。

ローカルの構成とコンテナ内の構成が同じになるように、src ディレクトリをコンテナ側の /usr/src/app/src にマウント しています。

各コンテナで使用する環境変数の設定ファイル .env は、先ほど解説したとおり、.env.template を元に作成します。

各コンテナのポートの設定により、コンテナの起動後、Node.js コンテナは http://localhost:53001 から Redmine コンテナには http://localhost:53000 からアクセスできるようになります。

※ ホストが localhost 以外の場合は、「 localhost 」の部分を変更してアクセスしてください。

src/routes/index.js

カスタム API の実装ファイルです。

中身は以下の通り。

var express = require('express');
var router = express.Router();
var axios = require('axios');

// 起動確認用ページ
router.get('/', function (req, res) {
    res.render('index', {title: 'Express'});
});

// API リクエストのデフォルト設定
axios.defaults.baseURL = 'http://' + process.env.REDMINE_API;
axios.defaults.headers = {
    'Content-Type': 'application/json',
    'X-Redmine-API-Key': process.env.API_ACCESS_KEY
};

// カスタム Projects API
var projects = express.Router();

// プロジェクトリスト表示
projects.get('/', async (req, res, next) => {
    const response = await axios.get('/projects.json').catch(next);
    res.send(response.data)
});

// プロジェクト検索
projects.get('/search', async (req, res, next) => {
    const response = await axios.get('/projects.json').catch(next);

    const result = [];
    for (let project of response.data.projects) {
        if (project.identifier.match(req.query.key)) {
            result.push(project)
        }
    }

    res.send(result)
});

// プロジェクト一括追加
projects.post('/addMany', async (req, res, next) => {
    const result = [];

    for (let identifier of req.body.identifiers) {
        const response = await axios.post('/projects.json', {
            project: {
                name: identifier,
                identifier: identifier
            }
        }).catch(next);

        result.push(response.data)
    }

    res.send(result)
});

// 全プロジェクトの削除
projects.delete('/all', async (req, res, next) => {
    const response = await axios.get('/projects.json').catch(next);

    const result = [];
    for (let project of response.data.projects) {
        const response = await axios.delete(
            `/projects/${project.identifier}.json`
        ).catch(next);

        result.push(response.data)
    }

    res.send(result)
});


router.use('/projects', projects);

module.exports = router;

コード中で使用している Redmine API の詳細は、以下のページを参照してください。

https://www.redmine.org/projects/redmine/wiki/Rest_Projects

コードの内容は大きくわけて、以下の3つです。

  • 起動確認用ページの設定
  • APIリクエストのデフォルト設定
  • カスタム Projects API の実装
起動確認用ページの設定

Express ジェネレータが生成したものを、そのまま残しています。コンテナの起動の確認に使用 します。

APIリクエストのデフォルト設定

Redmine API にアクセスするために、baseUrlheaders を設定しています。

headers では API アクセスキーを設定しています。

環境変数 REDMINE_APIAPI_ACCESS_KEY は、.env ファイルで指定した値が使用されます。

カスタム Projects API の実装

以下の4つのカスタム API を実装しています。

  • プロジェクトリストの表示
  • プロジェクトの検索
  • プロジェクトの一括追加
  • 全プロジェクトの削除

「プロジェクトリストの表示」 API は、Redmine API のプロジェクト取得をそのまま返しています。

プロジェクト操作の結果確認用に追加しています。

「プロジェクトの検索」 API は、リクエストクエリで キーワードを指定して、プロジェクトの indentifier にキーワードが含まれるプロジェクトだけを取得 します。

「プロジェクトの一括追加」 API は、複数の identifier を配列で指定して、一括でプロジェクトを追加 します。

「全プロジェクトの削除」 API は、その名の通り、全てのプロジェクトを削除 します。

どれも、お試し用のものなので、実際に使えるというものではありません。

特に、「全プロジェクトの削除」 API を実際に利用する場合は、細心の注意を!

実際に、カスタム API を使ってみる!

環境変数ファイルの設置

以下のコマンドを実行して、.env ファイルを追加してください。

cp .env.template .env

コンテナの起動

次に、コンテナを起動します。

以下のコマンドでコンテナを起動してください。

docker-compose up -d

http://localhost:53001( Node.js コンテナ ) とhttp://localhost:53000( Redmine コンテナ ) にアクセスして、コンテナが起動しているか確認してください。

Node.js は、以下のような表示がされれば起動成功です!

Node.js で Redmine にカスタム API を追加してみる

Redmine の方は、ログイン画面が表示されれば起動成功です。

※ Redmine コンテナは起動に少し時間がかかります。

APIアクセスキーを取得&設定

「APIアクセスキー」を起動した Redmine から取得して、.env ファイル内の [APIアクセスキー] の部分を取得したアクセスキーと置き換えます。

「APIアクセスキー」は、以下のリンクを参考にして、取得してください。

Redmine API を試してみる【 curl 編 】 - API アクセスキーの確認

APIアクセスキーを設定したら、以下のコマンドを実行して Node.js コンテナを再起動してください。

docker-compose up -d node

カスタム API にリクエスト!

準備が整ったので、いよいよ、実装したカスタム API を使ってみます。

curl コマンドがローカル環境にない場合を考慮して、リクエストは Node.js コンテナ内から送ることにします。

以下のコマンドで、Node.js コンテナの中へ入ってください。

docker-compose exec node bash

ホスト環境に curl コマンドがある場合は、ホストからのアクセスでも問題ありません。

ホスト環境からリクエストする場合は、以下の curl コマンドで localhost:3000localhost:53001 に置き換えてコマンドを実行してください。

プロジェクトリストの表示

まずは、プロジェクトリストを表示してみます。

$ curl localhost:3000/projects
{"projects":[],"total_count":0,"offset":0,"limit":25}

プロジェクトをまだ追加していないので、projects の中身は空 になっています。

プロジェクトの一括追加

次に、プロジェクトを一括で追加してみます。

appleapple-sandboxorangeorange-sandbox の4つのプロジェクトを追加してみます。

$ curl -X POST localhost:3000/projects/addMany -d '{"identifiers":["apple","apple-sandbox","orange","orange-sandbox"]}' -H "Content-Type: application/json"
[{"project":{"id":5,"name":"apple","identifier":"apple","description":null,"homepage":"","status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"}},{"project":{"id":6,"name":"apple-sandbox","identifier":"apple-sandbox","description":null,"hom
epage":"","status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"}},{"project":{"id":7,"name":"orange","identifier":"orange","description":null,"homepage":"","status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:
25Z","updated_on":"2020-05-10T21:07:25Z"}},{"project":{"id":8,"name":"orange-sandbox","identifier":"orange-sandbox","description":null,"homepage":"","status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:25Z","updated_on":"2020-05-10T21:07:25Z"}}]

appleapple-sandboxorangeorange-sandbox の4つのプロジェクトが追加されました。

Redmine の「プロジェクト」ページを開いて、プロジェクトが追加されているか確認してください。

以下のように、4つのプロジェクトが表示されていれば、プロジェクトの一括追加は成功です!

Node.js で Redmine にカスタム API を追加してみる

念のため、「プロジェクトリストの表示」 API でも、プロジェクトが追加されているか確認しておきます。

$ curl localhost:3000/projects
{"projects":[{"id":5,"name":"apple","identifier":"apple","description":null,"status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"},{"id":6,"name":"apple-sandbox","identifier":"apple-sandbox","description":null,"status":1,"is_public":true,"
inherit_members":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"},{"id":7,"name":"orange","identifier":"orange","description":null,"status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:25Z","updated_on":"2020-05-10T21:07:25Z"},{"id":8,"name":"orange-
sandbox","identifier":"orange-sandbox","description":null,"status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:25Z","updated_on":"2020-05-10T21:07:25Z"}],"total_count":4,"offset":0,"limit":25}

先ほどは空だった projects フィールドに、appleapple-sandboxorangeorange-sandbox の4つのプロジェクトが追加されました。

プロジェクトの検索

続いて、「プロジェクトの検索」APIを試してみたいと思います。

プロジェクトの identifier に「 apple 」の文字列が含まれるプロジェクトを取得します。

$ curl localhost:3000/projects/search?key=apple
[{"id":5,"name":"apple","identifier":"apple","description":null,"status":1,"is_public":true,"inherit_members":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"},{"id":6,"name":"apple-sandbox","identifier":"apple-sandbox","description":null,"status":1,"is_public":true,"inherit_memb
ers":false,"created_on":"2020-05-10T21:07:24Z","updated_on":"2020-05-10T21:07:24Z"}]

上の結果のように、appleapple-sandbox プロジェクトだけが表示されれば、プロジェクトの検索は成功です!

全プロジェクトの削除

最後に、追加したプロジェクトを一括で削除しておきます。

$ curl -X DELETE localhost:3000/projects/all
["","","",""]

Redmine の「プロジェクト」ページをリロードして、プロジェクトが全て削除されているか確認してください。

プロジェクトが全て消えていれば、削除リクエストは成功です。

念のため、「プロジェクトリストの表示」 API でも、プロジェクトが削除されているか確認しておきます。

$ curl localhost:3000/projects
{"projects":[],"total_count":0,"offset":0,"limit":25}

上の結果のように、projects の中身が空になっていればOKです。

コードを編集・調整する場合は

package.jsonを編集したときは

以下のコマンドで node イメージを再ビルド してください。

docker-compose build --no-cache node

src 内のコードを編集したときは

以下のコマンドで Node.js コンテナを再起動 してください。

docker-compose restart node

後始末

起動したコンテナを停止しておきます。

以下のコマンドで、起動したコンテナを停止してください。

docker-compose down

データの永続化設定はしていないので、再起動したときは、再度、APIアクセスキーの設定が必要になります。 注意してください。

まとめ

今回は、Node.js を使って、Redmine にカスタム API を追加 してみました!

Node.js に限らず、外部 API サーバを設置することで Redmine API を幅広く、拡張することができそうです

Redmine API には、Projects や Issues、Users などの主要な部分には、必要十分な、基本的な操作が用意されています。

Redmine API では対応できない場合でも、直接 DB を操作することが可能 なので、いろいろとカスタムできそうな感じです。

最後に、簡単に今回の内容をまとめてみたいと思います。

  • 外部 API サーバを設置すれば、Redmine API を拡張できる!
  • カスタムの自由度は高め
  • いざとなったら、直接 DB をさわる手もあるよ(´・ω・`)b