## 概要 APIにリクエストしたユーザーにDMでSlack通知するAPIをつくってみる。 > [!warning] > プロダクションセキュアな実装ではないので注意。 ## プロジェクト作成 ```console uv init drf-slack-notification cd drf-slack-notify rm .python-version main.py README.md mise use [email protected] ``` ### 依存パッケージの追加 ``` $ uv add django==4.2 djangorestframework "psycopg[binary,pool]" django-stubs Installed 11 packages in 93ms + asgiref==3.10.0 + django==4.2 + django-stubs==5.2.7 + django-stubs-ext==5.2.7 + djangorestframework==3.16.1 + psycopg==3.2.10 + psycopg-binary==3.2.10 + psycopg-pool==3.2.6 + sqlparse==0.5.3 + types-pyyaml==6.0.12.20250915 + typing-extensions==4.15.0 ``` ### プロジェクトとアプリの作成 ```console v django-admin startproject drf_slack_notification cd drf_slack_notification django-admin startapp main ``` 構成はこうなる。 ```  drf-slack-notification ├──  drf_slack_notification │ ├──  __init__.py │ ├──  asgi.py │ ├──  main │ │ ├──  __init__.py │ │ ├──  admin.py │ │ ├──  apps.py │ │ ├──  migrations │ │ │ └──  __init__.py │ │ ├──  models.py │ │ ├──  tests.py │ │ └──  views.py │ ├──  settings.py │ ├──  urls.py │ └──  wsgi.py ├──  manage.py ├──  mise.toml ├──  pyproject.toml └──  uv.lock ``` ## 不要ファイルの削除 必要な情報に集中してシンプルにしたいので不要ファイルを削除する。といっても `main` から `admin.py` と `tests.py` を削除しただけなのでほとんど変わっていない。 ```  drf-slack-notification ├──  drf_slack_notification │ ├──  __init__.py │ ├──  asgi.py │ ├──  main │ │ ├──  __init__.py │ │ ├──  apps.py │ │ ├──  migrations │ │ │ └──  __init__.py │ │ ├──  models.py │ │ └──  views.py │ ├──  settings.py │ ├──  urls.py │ └──  wsgi.py ├──  manage.py ├──  mise.toml ├──  pyproject.toml └──  uv.lock ``` ## [[Django REST framework|drf]]とDBの設定 ### Pythonファイルの設定 `settings.py` の `INSTALLED_APPS` に `rest_framework` を追加。 ```python INSTALLED_APPS = [ "rest_framework", ] ``` `settings.py` の設定を変更。 ```python DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "drfdb", "USER": "postgres", "PASSWORD": "password", "HOST": "127.0.0.1", "PORT": "15432", } } ``` ### DBコンテナの設定 [[PostgreSQL]]に関するファイルを追加。 `docker-compose.yml` ```yaml services: db: image: postgres:18 container_name: postgres-drf ports: - 15432:5432 volumes: - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d environment: POSTGRES_PASSWORD: password ``` `docker-entrypoint-initdb.d/init.sql` ```sql CREATE DATABASE drfdb; \c drfdb; ``` ### DBの起動 起動。 ```console docker compose up -d ``` ### マイグレーション ```console python manage.py makemigrations python manage.py migrate ``` ## ユーザー取得APIの作成 ### ユーザーの追加 ```console python manage.py createsuperuser --username Minerva --email [email protected] ``` ### コードの実装 `main/serializers.py` ```python from django.contrib.auth.models import User from rest_framework import serializers class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ["url", "username", "email"] ``` `main/views.py` ```python from django.contrib.auth.models import User from rest_framework import permissions, viewsets from drf_slack_notification.main.serializers import UserSerializer class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] ``` `urls.py` ```python from django.urls import include, path from rest_framework import routers from drf_slack_notification.main import views router = routers.DefaultRouter() router.register(r"users", views.UserViewSet) urlpatterns = [ path("", include(router.urls)), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), ] ``` ### 動作確認 ```console python manage.py runserver ``` `GET http://localhost:8000/users/` ```json [ { "url": "http://localhost:8000/users/2/", "username": "Minerva", "email": "[email protected]" } ] ``` ## Slackとの連携について APIレベルでの下準備は以下で調査済。 <div class="link-card-v2"> <div class="link-card-v2-site"> <img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" /> <span class="link-card-v2-site-name">Minerva</span> </div> <div class="link-card-v2-title"> 📜2025-10-13 Slackアプリと連携してアプリからユーザーに通知できるようにする </div> <div class="link-card-v2-content">自作SaaSプロダクトでSlack通知機能をAPIレベルで実現するため、OAuth 2.0フローを用いてBotトークンやユーザートークンを取得し、Slackアプリ作成やリダイレクトURI設定、chat.postMessageやconversations.open APIを利用した通知方法、必要なスコープ設定、ユーザー通知情報の保存方法などを検証した。</div> <img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Notes/attachments/activity.webp" /> <a data-href="📜2025-10-13 Slackアプリと連携してアプリからユーザーに通知できるようにする" class="internal-link"></a> </div> %%[[📜2025-10-13 Slackアプリと連携してアプリからユーザーに通知できるようにする]]%% ### プロダクトSlack連携有効のシーケンス図 ```mermaid sequenceDiagram autonumber actor Administrator participant Web participant API participant DB participant SlackAuth participant SlackAPI Administrator ->> Web: 「Slack連携を有効にする」 Web ->> SlackAuth: GET https://slack.com/oauth/v2/authorize<br/>client_id, scope=chat:write:im:write, redirect_uri SlackAuth ->> Administrator: 同意画面 Administrator ->> SlackAuth: 同意する SlackAuth ->> Web: 302 リダイレクト<br/>code Web ->> API: POST /oauth/bot/callback<br/>code API ->> SlackAPI: GET /oauth.v2.access<br/>code, clientId, clientSecret SlackAPI ->> API: app_id, user_id(Slack), access_token, bot_user_id, team_id API ->> DB: INSERT t_slack_bot<br/>tenant_id, team_id, app_id, access_token DB ->> API: success API ->> Web: success Web ->> Administrator: success ``` ### ユーザーごとのSlack通知有効のシーケンス図 ```mermaid sequenceDiagram autonumber actor AuthedUser participant Web participant API participant DB participant SlackAuth participant SlackAPI AuthedUser ->> Web: 「Slack通知を有効にする」 Web ->> SlackAuth: GET https://slack.com/oauth/v2/authorize<br/>client_id, user_scope=openid, redirect_uri SlackAuth ->> AuthedUser: 同意画面 AuthedUser ->> SlackAuth: 同意する SlackAuth ->> Web: 302 リダイレクト<br/>code Web ->> API: POST /oauth/user/callback<br/>code API ->> SlackAPI: GET /oauth.v2.access<br/>code, clientId, clientSecret SlackAPI ->> API: app_id, user_id(Slack), access_token, team_id API ->> DB: INSERT t_slack_user<br/>tenant_id, user_id, team_id, user_id(Slack) DB ->> API: success API ->> Web: success Web ->> AuthedUser: success ``` ### Slack通知のシーケンス図 ```mermaid sequenceDiagram autonumber participant API/Lambda participant DB participant SlackAuth participant SlackAPI API/Lambda ->> DB: SELECT t_slack_user<br/>WHERE tenant_id, user_id = 通知ユーザーID DB ->> API/Lambda: team_id, user_id(Slack), channel_id API/Lambda ->> DB: SELECT t_slack_bot<br/>WHERE tenant_id, team_id DB ->> API/Lambda: access_token alt channel_idが登録されていなかった API/Lambda ->> SlackAPI: POST /conversations.open<br/>users = 通知ユーザーID, Bearer(access_token) SlackAPI ->> API/Lambda: channel_id API/Lambda ->> DB: UPDATE t_slack_user(channel_id)<br/>WHERE tenant_id, user_id = 通知ユーザーID DB ->> API/Lambda: success end API/Lambda ->> SlackAPI: POST /chat.postMessage<br/>channel_id, message, Bearer(access_token) SlackAPI ->> API/Lambda: success ``` ## ユーザー拡張テーブルの追加 > [!todo] >