[Notion API x Slack API in Rails] 即席お問い合わせ管理システム構築
バヅクリの合原です。
とある新規開発PJにて、リリース1週間前でしたが、
「問い合わせ機能必要ではないか?」となり、、、
かねてから、使ってみたかったNotionを使えないか?と。実際に使ってみたら、
当初の想定以上に、いいものができたので、まとめたいと思います。
前提
前提として、今回の環境は、下記のフロントエンドとバックエンドの分離構成であります。
- Backend - Rails
- Frontend - Nextjs -- こちらの実装については、割愛。
何をやったか
- Nextjsにてモーダル表示ー>POST送信
- Rails側でNotionApiリクエストし、問い合わせ内容をPageとして保存
- 上記でできた NotioページのURLをSlackAPIへリクエストして、Slack通知
処理フローの設計
前述の分離構成から、フロントエンドでは、表示・描画に専念ということで、
RailsからNotion APIへリクエストをする方針としました。
また、
Notionには標準でslack連携機能があるのですが、
こちらは、些細な更新でも、設定したslackチャンネルに通知が来てしまうため(=過剰)、
別で、Slack Apiを用いて、問い合わせたあった場合のみ通知を実行すること、としました。
👇シーケンス(っぽい)図
Notionデータベースの基本
さて、Notion Api自体初めてでしたので、まずは公式ドキュメントを拝見し、
Notionの(データの)構成を確認します。
今回触れた箇所にスコープして、簡単にまとめたものは以下、引用の通り。
Notion APIのデータ構造を実際にAPIを叩きながら理解する
データ構造イメージ
Database > Page > Block(Page内に追加するコンテンツ総称)
[下準備]database id を控えておく
これに従い、事前に、
下記の「問い合わせ一覧ページ」を新規作成しておきます
=>こちらが、問い合わせ内容の保存対象となるDatabseとなります。
下記の通り、新規作成ページURLからdatabase idを取得しておきます。
※後ほど、Notion APIリクエストで使います。
今回は下記の
その上で、前述のAPI操作にて、今回の仕組みを実装したいと思います。
Notion APIの使い方(概要)
の通り、
- NotionではIntegrationというものを作成し、
- こちらを特定のNotionのページやワークスペースにインストールして使います。
言い換えるなら、Integrationとは、
Notion上で使えるカスタムアプリケーション
といったところでしょうか。FacebookアプリやZoomアプリ等と似てますねw
Integration Typeとは?
Integrationには以下のような種類があります。
- Public
- Internal
※詳細な説明は公式Docsを参照
今回は、会社のアプリケーションの一部として使いますので、後者のInternalを使います。
[下準備]Integration作成
Visit https://www.notion.com/my-integrations in your browser.
上記URLよりIntegrationの作成を行います。
[下準備]各種機能の設定をする(後から変更も可能)
一言で言うと、
Integrationのpermissionの設定ですかね。
[下準備]作成したIntegrationのシークレットトークンを控えておく
👇こちらを後ほど、使うので控えておきます。
[下準備]作成したIntegrationを追加する
Step 2: Share a database with your integration
You must share specific pages with an integration in order for the API to access those pages
作成したIntegrationを追加します。これによって、
Integrationが該当ページの編集権限を持つ状態になります。
つまり、Notion APIを使って、ページの編集が可能となります。
👇APIで操作したいNotionページの右上の3点リーダーから
👇「コネクトの追加」にて、作成したIntegration名で検索すると、Integrtationが見つかるので「追加」をする
ここまでで、 Notion側の準備は完了です。
Notion API ≒ RESTful API
The Notion API follows RESTful conventions when possible, with most operations performed via GET, POST, PATCH, and DELETE requests on page and database resources. Request and response bodies are encoded as JSON.
認証・認可方法
Requests use the HTTP Authorization header to both authenticate and authorize operations. The Notion API accepts bearer tokens in this header. Bearer tokens are provided to you when you create an integration.
ポイント
- HTTP Authorization headerにBearer tokensを付与してリクエストすること
- Bearer tokens=Itengrationのシークレットトークン
curlでAPIリクエストしてみる
実装に入る前にまずは、サクッとcurlで確認です。
必要なのは、下記。
- 作成したItengrationシークレットトークン
- 事前に控えていたdatabase id
curl -X POST https://api.notion.com/v1/pages \
-H "Authorization: Bearer [ここにシークレットトークン]" \
-H "Content-Type: application/json" \
-H "Notion-Version: 2021-08-16" \
--data "{
\"parent\": { \"database_id\": \ [ここにdatabase id]\" },
\"properties\": {
\"title\": {
\"title\": [
{
\"text\": {
\"content\": \"問い合わせ内容本文サンプル\"
}
}
]
}
}
}"
とすると、期待通り、データが保存されます。
APIリクエストの方法がわかったところで、あとは、これを
Railsにて実装するのみです。
Notion APIリクエスト実装 in Rails
Notion APIは、下記の通り、ドキュメントが非常にわかりやすく、整備されています。
※zoom APIにそっくりw
設計
今回は、Nextjsから問い合わせリクエストをRailsで受け付けた上で、
Notion API、Slack APIへリクエストするため、
form objectにて、この責務を担うこととします。
..今回に限らず、API経由でのリソースの操作が統一的にできる作りになっているので、
下記のようにNotion APIリクエストをするmoduleを作成し、名前空間も統一することとしました。
こうしておけば、追加で必要になったタイミングで機能の拡張もしやすいため。
app/models/notions/
├── base.rb
├── databases
├── pages
│ └── post.rb
└── pages.rb
以下、本論に関わるところ以外は、一部改変しております。悪しからずです。
APIリクエストするbase class作成
class Notions::Base
include ActiveModel::Model
class ApiError < StandardError; end
validates :secret_token, presence: true
private
def api_request
raise NotImplementedError, "Implement #{self.class.name}#token_request"
end
def headers
{
"Content-Type" => "application/json",
# https://developers.notion.com/reference/versioning
"Notion-Version" => versioning_date
}.merge(token_request_headers)
end
def versioning_date
raise NotImplementedError, "Implement #{self.class.name}#versioning_date"
end
def token_request_headers
{
"Authorization" => "Bearer #{secret_token}"
}
end
def secret_token
Rails.application.credentials.notion[Rails.env.to_sym][:api_key]
end
def base_url
"https://api.notion.com/v1/"
end
end
上記を継承する形で、各リソースへのAPIリクエストをするclassを作成
今回は、問い合わせがあるたびに、
databaseにpageを作成するため、
app/models/notions/[リソース名]/[httpリクエスト名].rb
としました。
実装については、
に従い、
request_bodyにて、自由に必要なパラメータを受け取れるようにしておきました。
class Notions::Pages::Post < Notions::Base
class ApiError < StandardError; end
REQUIRED_ATTRS = %i[
parent
properties
].freeze
OPTIONAL_ATTRS = %i[
children
icon
cover
].freeze
(REQUIRED_ATTRS + OPTIONAL_ATTRS).each do |attr|
attr_reader attr
attr_accessor attr if OPTIONAL_ATTRS.include? attr
validates attr, presence: true if REQUIRED_ATTRS.include? attr
end
def initialize(parent, properties, attributes = {})
@parent = parent
@properties = properties
super(attributes)
end
def create!
return raise ArgumentError, errors.full_messages.join(", ") if invalid?
raise ApiError, "#{response_body['code']}, #{response_body['message']}" unless api_response.code == "200"
response_body
end
private
def response_body
@response_body ||= JSON.parse(api_response.body)
end
def api_response
uri = URI.parse(api_request_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
@api_response ||= http.post(uri.path, request_body.to_json, headers)
end
def api_request_url
base_url + Notions::Pages::RESOURCES
end
def versioning_date
"2021-08-16"
end
# https://developers.notion.com/reference/post-page
# https://developers.notion.com/reference/parent-object
def request_body
request_body = {
parent: parent,
properties: properties
}.merge(children)
request_body.merge(children) if children.present?
end
end
問い合わせを受け付ける Form Objectにて、リクエストを実行
class Inquiry
class SendgridAPIError < StandardError; end
include ActiveModel::Model
include SendgridMailable
MAIL_FROM = "noreply@hoge.com".freeze
MAIL_TEMPLATE_ID = "hogehoge-id".freeze
define_model_callbacks :save, only: :after
after_save :client_thanks_mail!
ATTRS = %i[
category_name
description
name
email
].freeze
ATTRS.each do |a|
attr_accessor a
validates a, presence: true
end
def save
return false if invalid?
run_callbacks :save do
res = notion_post.create!
slack_post!(res)
true
rescue StandardError, Notions::Pages::Post::ApiError => e
errors.add :base, e.message
false
end
end
private
def client_thanks_mail!
# 問い合わせ完了時のメール送信処理
end
def api_request_response
@api_request_response ||= send_sendgrid_mail(from: MAIL_FROM, template_id: MAIL_TEMPLATE_ID) do
[[email, template_data]]
end
end
def template_data
{ category_name: category_name, description: description }
end
def slack_post!(notion_response)
# slack apiリクエスト実行処理
end
def api_request_success?
SendgridResult::SUCCESSFUL_RESPONSE_CODE_RANGE.cover? api_request_response.status_code.to_i
end
#------------ notion -----------
def notion_post
@notion_post ||= Notions::Pages::Post.new(parent, properties, children: children)
end
def parent
{ database_id: database_id }
end
def database_id
Rails.application.credentials.notion[Rails.env.to_sym][:databases][:survey][:inquiry]
end
# リクエストbodyに含める問い合わせ内容を組み立てる
def properties_mappings
{
"内容": description.truncate(30),
"カテゴリ": category_name.to_s,
"企業名": "#{name}",
"ユーザー": name,
"メールアドレス": email,
}
end
def properties
properties_mappings.each_with_object({}) do |(k, v), h|
h[k.to_s] = if k.to_s == "内容"
{
title: [
{
text: {
content: v,
}
}
]
}
else
{
rich_text: [
{
text: {
content: v
}
}
]
}
end
end
end
# https://developers.notion.com/reference/block#block-type-object
def children
{
children: [
{
object: "block",
type: "heading_1",
heading_1: {
rich_text: [{ type: "text", text: { content: "設問カテゴリ" } }]
}
},
{
object: "block",
type: "heading_2",
heading_2: {
rich_text: [{ "type": "text", "text": { content: category_name.to_s } }]
}
},
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [
{
type: "text",
text: {
content: description,
}
}
]
}
}
]
}
end
end
全てを掲載はできないため、一部、割愛しているところもありますが、
大体はこういった流れで、実装完了。
Nextjsからのリクエストに応じて、このFormObjectをコントローラから実行する状態にして、完了。
Slack API
こちらの実装については、今回は割愛。
また、別で披露する機会ありましたら!
まとめ
ドキュメントもわかりやすく、簡単に扱えるNotion APIを今回初めて使ってみました。
実際、リリース1週間前という時間がない中での導入でしたが、狙い通り、
Notion APIを使うことで、Rails側の実装も最小限に抑え、
ローコード開発ができた良い事例となりました。
また、バヅクリでは、通常業務においてもNotion自体利用しているため、今後より多くの場面でAPIを使った自動化やローコード開発を取り入れていきたいと思えた経験になりました。
その際はまた、改めて披露させていただきますw
現在Railsエンジニアを募集しております!
さてさて、
そんなバヅクリ開発チーム=DXチームでは、現在Railsエンジニアを募集しております!
気になる方はぜひ👇より!
こちらにコメントでもいいので、カジュアルにご連絡いただけたらな、と思います!
Discussion