LINEを使った通知機能の作成のために、ユーザー登録と後から連携を実施したい

## なぜ実現したいのか。

- LINEを使ったユーザー登録を実装済み
- LINEを使った通知機能をユーザーに使ってもらいたい
- 今の実装だと、LINEを使ったユーザー登録にしかできず、LINEだけの紐づけが出来ずに既存のユーザーがLINEとの連携が出来ない

という状況を改善するために本記事を記載して、自分の実装内容についてまとめていく。

## 今回の記事の内容について

まずは、LINEを使ったユーザー登録の記載とMessingAPIの導入についての流れを記載してから、その後LINEとの連携だけを実装する内容を記事にします。

### 技術構成

| カテゴリ | 技術内容 |
|---|---|
| サーバーサイド | Ruby 3.2.3 |
| フレームワーク | Rails 7.2.1 |
| フロントエンド | HTML, CSS, Bootstrap, Hotwire（JS） |
| 開発環境 | Docker |

## 事前処理

### LINEを使ったユーザー登録

sorceryを使ったサブモジュールでの登録
今回はsorceryを使った認証機能のため、sorceryのサブモジュールを使っての認証機能を作成しています。
https://github.com/Sorcery/sorcery/wiki/External
sorceryのGEMを参考に実装していく。以下に実装の内容をまとめる。

1. LINEログインAPIの取得
2. sorceryのサブモジュールのインストール
3. 外部認証用のテーブルの作成（**authenticationsテーブル**）
4. モデルファイルで関係性の定義
5. `config/initializers/sorcery.rb` で受け取るLINEの設定
6. `/oauths_controller.rb` の作成をしてログインについての処理の記載
7. ルーティングの記載
8. ビューファイルで配置
9. ngrokの導入でテスト環境をHTTPS通信に切り替えて実装

### MessingAPIの導入

実装済みについて

- 既に実装済みのnotificationsテーブルを生かして実装。
- アプリケーション内では通知は受け取る前提に。
- LINEの通知のONとOFFだけを切り替えられるように、usersテーブルにline_notification_enabledを作成する

以下に実装順序と実装をしたこと

1. LINEのMessingAPIの導入
2. ユーザーテーブルに`line_notification_enabled`の作成
3. `line-bot-api` GEMの導入
4. NotificationsControllerを使って、LINEのメッセージが届くように実施

## メインの実装（※ここから本題です）

### 問題の確認

冒頭で述べた通りですが改めて実装内容としては以下の通りです。

1. 「LINEでの通知を実施したい場合、ユーザーにはLINEのユーザーIDを保存する必要がある」
2. 「現状がLINEでのユーザー登録及びログインしか出来ず、ログイン済みのユーザーは紐づける方法がない」

という２点が実装中でおきた問題です。

1. **「LINEでの通知を実施したい場合、ユーザーにはLINEのユーザーIDを保存する必要がある」**
    
    問題：sorceryでの認証として必要なことと、LINE固有のIDが紐づけできていないこと
    
    理由：保存先がusersテーブルの、emailで受け取るようにしている
    
    解決：LINEの固有のIDを受け取るためのカラムを追加してそれで保存すること
    
2. **「現状がLINEでのユーザー登録及びログインしか出来ず、ログイン済みのユーザーは紐づける方法がない」**
    
    問題：sorceryでの認証ではLINEとの連携が出来ない
    
    理由：Sorceryの外部認証は新規ユーザー作成を前提とした設計であること
    
    解決：omniauth-lineを導入して、かつ、別のLINEのログインAPIも導入してコールバック先も分けて実装
    

という実装をしていきます。

### LINEのユーザーIDを保存するように変更

1. users テーブルに line_user_id カラムを追加と、email カラムの null: false 制約を削除
2. config/initializers/sorcery.rb内のマッピングの内容を変更
email→line_user_idに変更
    
    ```
      # LINE認証に関すること
      config.line.key = ENV['LINE_Sorcery_ID']
      config.line.secret = ENV['LINE_Sorcery_SECRET']
      # 本番環境のURLに置き換える
      config.line.callback_url = '<https://smart-phone-choice.com/oauth/callback?provider=line>'
      #ユーザーを識別するためのOpenID Connect IDトークンとメールアドレス
      config.line.scope = "profile openid email"
      config.line.bot_prompt = 'aggressive'
      # 以下の内容にline_user_idを使う
      config.line.user_info_mapping = { name: 'displayName', line_user_id: 'userId' }
    
    ```
    
3. app/controllers/oauths_controller.rb 内に、skip_before_action :require_loginを追記する

### omniauth-lineを導入して連携の処理のみの記載

1. Gemfile内にgem 'omniauth-line’の導入
    
    ```
    gem 'omniauth-line'
    gem 'omniauth-rails_csrf_protection' # CSRF対策
    ```
    
2. config/initializers/omniauth.rbの作成と編集
    
    ```
    Rails.application.config.middleware.use OmniAuth::Builder do
     # LINEのプロバイダーとスコープの指定
     provider :line, ENV['LINE_OmniAuth_ID'], ENV['LINE_OmniAuth_SECRET'],
               scope: 'profile openid email',
               bot_prompt: 'aggressive',
               # 本番環境のコールバックURLに変更する
               callback_url: '<https://smart-phone-choice.com/auth/line/callback?provider=line>' # 認証後にリダイレクトされるURL
    end
    ```
    
3. config/routes.rb の追加
    
    ```
      # 既存ユーザーのLINE連携用のLineConnectionsController用のルートを追加
      get '/auth/line/callback', to: 'line_connections#callback' # LINE連携ページへのルート
      post '/auth/line/callback', to: 'line_connections#callback' # LINE連携処理を行うルート
    ```
    
4. app/controllers/line_connections_controller.rbでコールバックの記載
    
    ```
    class LineConnectionsController < ApplicationController
      before_action :require_login
    
      def callback
        auth = request.env['omniauth.auth']
    
        if current_user.update(line_user_id: auth.uid)
          redirect_to my_profile_path, success: t('defaults.flash_message.line_success')
        else
          redirect_to my_profile_path, danger: t('defaults.flash_message.line_failure')
        end
      end
    end  
    ```
    
5. ビューファイルで条件先を埋め込む
    
    ```
      <% if current_user.line_registered? %>
        <p>LINEでの登録済み</p>
      <% else %>
        <!-- LINEの後から連携のユーザー用 -->
        <% if current_user.omniauth_user_linked_to_line? %>
          <p>LINEとの連携済み</p>
        <% else %>
          <%= button_to 'LINEと連携する', '/auth/line', method: :post, data: { turbo: false }, class: 'btn btn-success' %>
        <% end %>
      <% end %>
    ```
    

### MessingAPIとの連携。公式アカウントのリンク先の追加。

LINEログインAPI内の「チャンネル基本設定」の「友達追加オプション」にりんくされたLINEの公式アカウントとして追加する

念のため、公式アカウントのリンク先を追加（MessingAPIの友達かた結局通知が届くので）

```
<a href="<https://lin.ee/0NVtMsf>"><img src="<https://scdn.line-apps.com/n/line_add_friends/btn/ja.png>" alt="友だち追加" height="36" border="0"></a>
```

### 出来上がりのLINEのDevelopサイトと

!https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3683921/829c4930-9eb8-b416-116e-76346deb1519.png

SMC_testで、sorceryのサブモジュールで使うLINEを使ったユーザー登録
SMC連携でomniauth使う既存のユーザーのLINEとの連携
すまちょい公式でMessingAPIを使った通知

という風に分けています。

## まとめ

初学者の私にとっては、ログの内容を見ながら、どういう処理の中でどのようにしてデータが保存されているのかと確認しながらの実装で、テーブル設計の部分についてでしたので非常に勉強になった実装です。
また、データベースに保存する実施をしましたが、Webhookやトークンを使うことで、データベースに保存せずにMessingAPIを主体に通知機能も出来るのでは？
と思ったので、今後実装出来ればと思っています。
あくまで、LINEでのユーザー登録やMessingAPIの導入済みの方が、既存ユーザーにどう連携したらいいのか？と思った際に参考をしていただけると幸いです。

ありがとうございました。