このプロジェクトでは、Schemathesis を使用してFastAPIのOpenAPI定義を参照したスキーマに準拠した応答が返ってくるかのテストを自動化しています。このテストはGitHub Actionsで実行され、以下の内容を検証します:
- APIレスポンスがOpenAPIスキーマに準拠しているか
- エンドポイントが適切なステータスコードを返すか
- レスポンスタイムが許容範囲内か
- その他の一般的なAPIの問題
テスト結果はGitHub Actionsのアーティファクトとして保存され、問題が発生した場合に詳細な情報を確認できます。
本番環境と開発環境のデータベーススキーマを比較するワークフローを実装しています。このワークフローは以下の手順で実行されます:
- Tailscale GitHub Actionを使用してTailscaleネットワークに接続
- pg_dumpを使用して本番DBと開発DB(IP: 100.65.209.33, database: albot-dev)のDDLを抽出
- SchemaCrawlerを使用して両環境のスキーマを比較
- 差分があれば警告を表示し、詳細な差分情報をアーティファクトとして保存
- 各シャードからの認証には、Bearerトークンを使用します。
Authorization
Bearer <token>
sequenceDiagram
actor User as ユーザー
participant Front as フロントエンド
participant Back as バックエンド
participant Discord as Discord
User->>Front: /login にアクセス
Front->>Back: 認証URLリクエスト
Back-->>Front: 認証URL返却
Front->>Discord: リダイレクト
User->>Discord: Discordアカウントで認証
Discord->>Front: /callback にリダイレクト
Front->>Back: 認証リクエスト
Note right of Back: 認証情報を<br/>sessionに保存
Back-->>Front: 認証完了レスポンス
GET
: OAuth2認証のリダイレクトURLを取得します。
Request Parameters
- redirect (query): リダイレクト先のURL
Response
{
"message": "Success",
"data": {
"url": "https://discord.com/api/oauth2/authorize?..."
}
}
POST
: OAuth2認証のコールバック処理を行います。
Request Parameters
- code (query): 認証コード
- state (query): 認証状態
Response
{
"message": "Success"
}
POST
: ログアウト処理を行います。
Response
{
"message": "Success"
}
GET
: シャードの一覧を取得します。
Request Parameters
- status (query): シャードのステータス(online, offline, all)、デフォルトは "all"
Response
{
"message": "Success",
"data": {
"ids": [
0,
1,
2
]
}
}
GET
: シャードの割当を行い、環境変数を配信します。
Response
{
"message": "Success",
"data": {
"shard_count": 10,
"shard_id": 0,
"discord_token": "token",
"sentry_dsn": "dsn",
"tts_key": "key",
"heartbeat_token": "token"
}
}
POST
: シャードの終了時に、割当を解除します。
Response
{
"message": "Success"
}
GET
: シャードに接続するサーバーの接続コマンドを取得します。
Options- changes_only (boolean): 前回fetch以降に更新されたコマンドのみ取得します。epoch秒で指定します。
{
"message": "Success",
"data": {
"commands": {
"123456789012345678": "t.con",
"234567890123456789": "召喚"
}
}
}
POST
: シャードのメトリクスを更新します。
Request Body
{
"guilds": 10000,
"connected": 100
}
Response
{
"message": "Success"
}
- /me/
ログイン中のユーザーにアクセス。セッションで認証されます。 - /{user_id}/
任意のユーザーの情報にアクセス。bearerトークンでの認証が必要です。
GET
: ログイン中のユーザー情報を取得します。
Response
{
"message": "Success",
"data": {
"info": {
"id": "123456789012345678",
"username": "username",
"avatar": "avatar_hash",
"discriminator": "1234",
"public_flags": 0,
"flags": 0,
"bot": false,
"system": false,
"banner": null,
"accent_color": null,
"global_name": "Global Name",
"avatar_decoration_data": null,
"mfa_enabled": false,
"locale": "ja",
"premium_type": null,
"email": "[email protected]",
"verified": true
}
}
}
GET
: ユーザーのサブスクリプションを取得します。
Response
{
"message": "Success",
"data": {
"subscriptions": [
{
"sub_id": "sub_abcd1234",
"guild_id": 123456789012345678,
"plan": "monthly1",
"sub_start": "2021-01-01T00:00:00",
"last_updated": "2021-01-01T00:00:00",
"user_id": 123456789012345678
}
]
}
}
POST
: ユーザーのサブスクリプションを有効化します。
Request Body
{
"guild_id": 123456789012345678
}
Response
{
"message": "Success"
}
POST
: ユーザーのサブスクリプションをキャンセルします。
Response
{
"message": "Success"
}
POST
: ユーザーのサブスクリプションを更新します。
Request Body
{
"new_plan": "monthly1"
}
Response
{
"message": "Success"
}
GET
: ユーザーが所属するサーバーの一覧を取得します。
Request Parameters
- mutual (query): 相互のサーバーのみを取得するかどうか(デフォルト: true)
Response
{
"message": "Success",
"data": {
"guilds": [
{
"id": "123456789012345678",
"name": "サーバー名",
"icon": "icon_hash",
"banner": null,
"owner": true,
"permissions": "permissions",
"features": [
"FEATURE1",
"FEATURE2"
],
"approximate_member_count": 100,
"approximate_presence_count": 50
}
]
}
}
GET
: ユーザーが所属するサーバーの情報を取得します。
Response
{
"message": "Success",
"data": {
"info": {
"id": "123456789012345678",
"name": "サーバー名",
"icon": "icon_hash",
"banner": null,
"owner": true,
"permissions": "permissions",
"features": [
"FEATURE1",
"FEATURE2"
],
"approximate_member_count": 100,
"approximate_presence_count": 50
}
}
}
POST
: ユーザーのチェックアウトセッションを作成します。
Request Body
{
"plan": "monthly1"
}
Response
{
"message": "Success",
"data": {
"url": "https://example.com/"
}
}
POST
: サーバーのリソースを作成します。
Response
{
"message": "Success"
}
DELETE
: サーバーのリソースを削除します。
Response
{
"message": "Success"
}
GET
: 辞書の一覧を取得します。
Response
{
"message": "Success",
"data": {
"dict": {
"key1": "value1",
"key2": "value2"
}
}
}
PUT
: 辞書をリクエストデータで置き換えます。
Request Body
{
"dict": {
"key1": "value1",
"key2": "value2"
}
}
Response
{
"message": "Success"
}
DELETE
: 辞書を削除します。
Response
{
"message": "Success"
}
GET
: サーバーの読み上げ設定を取得します。
Response
{
"message": "Success",
"data": {
"settings": {
"guild_id": 731467468341510184,
"lang": "ja-JP",
"character_limit": 3000,
"speech_speed": 1.75,
"read_name": false,
"custom_voice": null,
"translate": false,
"read_name_on_join": true,
"read_name_on_leave": true,
"read_guild": false,
"read_not_joined_users": true,
"audio_api": "gtts"
}
}
}
DELETE
: サーバーの読み上げ設定を削除します。(初期化)
Response
{
"message": "Success"
}
POST
: サーバーの読み上げ設定を編集します。
Request Body
{
"speech_speed": 1.0,
"read_name": true
}
Response
{
"message": "Success"
}
GET
: サーバーの文字数使用状況を取得します。
Response
{
"message": "Success",
"data": {
"wavenet": {
"monthly_quota": 1000000,
"used_characters": 250000
},
"standard": {
"monthly_quota": 500000,
"used_characters": 150000
}
}
}
POST
: サーバーの文字数使用状況を更新します。文字数が増えた場合のみUPDATEします。
Request Body
{
"wavenet": {
"used_characters": 25000
},
"standard": {
"used_characters": 15000
}
}
Response
{
"message": "Success"
}
GET
: サーバーの設定を編集できるロールの一覧を取得します。
Response
{
"message": "Success",
"data": {
"enabled": true,
"role_ids": [
123456789012345678,
234567890123456789
]
}
}
PUT
: サーバーの設定を編集できるロールの一覧を更新します。
Request Body
{
"enabled": true,
"role_ids": [
123456789012345678,
234567890123456789
]
}
Response
{
"message": "Success"
}
POST
: サーバーのConnectionStateを生成して返却します。
payloadとして、接続コマンドで指定されたオプションを受け取ります。
BotクライアントのConnectionStateクラスに準拠したオブジェクトを返却します。
Request Body
{
"options": {
"vc_id": 123456789012345678,
"tc_id": 234567890123456789,
"read_guild": false,
"speech_speed": 1,
"lang": "jp",
"read_name": true
}
}
Response
{
"message": "Success",
"data": {
"connection_states": {
"guild_id": 123456789012345678,
"vc_id": 123456789012345678,
"target_id": 234567890123456789,
"service": "gtts",
"language_code": "ja-JP",
"translate": false,
"wavenet_voice": "ja-JP-Standard-A",
"standard_voice": "ja-JP-Standard-A",
"custom_voice": null,
"read_name": true,
"dict": {},
"dict_keys": [],
"speech_speed": 1.0,
"character_limit": 3000,
"character_usage": {
"wavenet": {
"monthly_quota": 1000000,
"used_characters": 0
},
"standard": {
"monthly_quota": 500000,
"used_characters": 0
}
},
"read_guild": false,
"read_name_on_join": true,
"read_name_on_leave": true,
"read_not_joined_users": true,
"unix_time_connected": 1741740034.4376345,
"sync_count": 0
}
}
}
GET
: サーバーのメッセージリンク展開設定を取得します。
Response
{
"message": "Success",
"data": {
"enabled": true
}
}
POST
: サーバーのメッセージリンク展開設定を更新します。
Request Body
{
"enabled": true
}
Response
{
"message": "Success"
}
GET
: サーバーの接続コマンドを取得します。
Response
{
"message": "Success",
"data": {
"command": "召喚"
}
}
PUT
: サーバーの接続コマンドを更新します。
Request Body
{
"command": "召喚"
}
Response
{
"message": "Success"
}
GET
: サーバーのサブスクリプションを取得します。
Response
{
"message": "Success",
"data": {
"subscriptions": [
{
"sub_id": "sub_abcd1234",
"guild_id": 123456789012345678,
"plan": "monthly1",
"sub_start": "2021-01-01",
"last_updated": "2021-01-01",
"user_id": 123456789012345678
}
]
}
}
POST
: 不具合のレポート(Quickレポート)を作成します。
Request Headers
- turnstile_token: Cloudflare Turnstileトークン
Request Body
{
"category": "connect",
"description": "問題の詳細な説明"
}
Response
{
"message": "Created a report."
}
GET
: メトリクスを取得します。
Response
{
"message": "Success",
"data": {
"metrics": {
"guilds": 10000,
"connected": 100
}
}
}
POST
: Stripeからのwebhookを処理します。
Response
{
"message": "Success"
}