lab_resource_manager/interface/slack/
gateway.rs

1//! Slackイベントゲートウェイ
2//!
3//! 受信したSlackイベントを適切なハンドラにルーティング
4
5use crate::domain::ports::repositories::ResourceUsageRepository;
6use crate::interface::slack::app::SlackApp;
7use crate::interface::slack::constants::*;
8use slack_morphism::prelude::*;
9use tracing::error;
10
11impl<R: ResourceUsageRepository + Send + Sync + 'static> SlackApp<R> {
12    /// スラッシュコマンドイベントをルーティング
13    ///
14    /// # 引数
15    /// * `event` - Slackからのスラッシュコマンドイベント
16    ///
17    /// # 戻り値
18    /// Slackに返すレスポンス
19    pub async fn route_slash_command(
20        &self,
21        event: SlackCommandEvent,
22    ) -> Result<SlackCommandEventResponse, Box<dyn std::error::Error + Send + Sync>> {
23        let command = event.command.0.as_str();
24
25        // user_id -> channel_id マッピングを更新
26        self.user_channel_map
27            .write()
28            .unwrap()
29            .insert(event.user_id.clone(), event.channel_id.clone());
30
31        match command {
32            "/reserve" => {
33                crate::interface::slack::slash_commands::reserve::handle(self, event).await
34            }
35            "/register-calendar" => {
36                crate::interface::slack::slash_commands::register_calendar::handle(self, event)
37                    .await
38            }
39            "/link-user" => {
40                crate::interface::slack::slash_commands::link_user::handle(self, event).await
41            }
42            _ => Ok(SlackCommandEventResponse::new(
43                SlackMessageContent::new().with_text(format!("不明なコマンド: {}", command)),
44            )),
45        }
46    }
47
48    /// インタラクションイベントをルーティング
49    ///
50    /// # 引数
51    /// * `event` - Slackからのインタラクションイベント(ボタンクリック、モーダル送信など)
52    ///
53    /// # 戻り値
54    /// View Submissionの場合はレスポンス(結果モーダルなど)を返す
55    pub async fn route_interaction(
56        &self,
57        event: SlackInteractionEvent,
58    ) -> Result<Option<SlackViewSubmissionResponse>, Box<dyn std::error::Error + Send + Sync>> {
59        match &event {
60            SlackInteractionEvent::ViewSubmission(view_submission) => {
61                self.route_view_submission(view_submission).await
62            }
63            SlackInteractionEvent::BlockActions(block_actions) => {
64                self.route_block_actions(block_actions).await?;
65                Ok(None)
66            }
67            SlackInteractionEvent::ViewClosed(_) => Ok(None),
68            _ => Ok(None),
69        }
70    }
71
72    /// ビュー送信イベントをルーティング(モーダル送信)
73    async fn route_view_submission(
74        &self,
75        view_submission: &SlackInteractionViewSubmissionEvent,
76    ) -> Result<Option<SlackViewSubmissionResponse>, Box<dyn std::error::Error + Send + Sync>> {
77        // callback_idを抽出してどのモーダルが送信されたかを判定
78        let callback_id = match &view_submission.view.view {
79            SlackView::Modal(modal) => modal.callback_id.as_ref().map(|id| id.to_string()),
80            _ => None,
81        };
82
83        match callback_id.as_deref() {
84            Some(CALLBACK_REGISTER_EMAIL) => {
85                crate::interface::slack::view_submissions::registration::handle(
86                    self,
87                    view_submission,
88                )
89                .await
90            }
91            Some(CALLBACK_LINK_USER) => {
92                crate::interface::slack::view_submissions::link_user::handle(self, view_submission)
93                    .await
94            }
95            Some(CALLBACK_RESERVE_SUBMIT) => {
96                crate::interface::slack::view_submissions::reserve::handle(self, view_submission)
97                    .await
98            }
99            Some(CALLBACK_RESERVE_UPDATE) => {
100                crate::interface::slack::view_submissions::update::handle(self, view_submission)
101                    .await
102            }
103            _ => {
104                error!("❌ 不明なcallback_id: {:?}", callback_id);
105                Ok(None)
106            }
107        }
108    }
109
110    /// ブロックアクションイベントをルーティング(ボタンクリック、セレクトメニューなど)
111    ///
112    /// # 引数
113    /// * `block_actions` - Slackからのブロックアクションイベント(ボタンクリック、セレクトメニューなど)
114    ///
115    /// # 戻り値
116    /// 正常に処理された場合は `Ok(())` を返す
117    ///
118    /// # エラー
119    /// 処理中にエラーが発生した場合は `Err` を返す
120    async fn route_block_actions(
121        &self,
122        block_actions: &SlackInteractionBlockActionsEvent,
123    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
124        // モーダル内のインタラクションを処理(viewがSome)
125        if block_actions.view.is_some() {
126            return self.route_modal_interactions(block_actions).await;
127        }
128
129        // メッセージ内のボタンを処理
130        let Some(actions) = &block_actions.actions else {
131            return Ok(());
132        };
133
134        for action in actions {
135            let action_id = action.action_id.to_string();
136
137            match action_id.as_str() {
138                ACTION_EDIT_RESERVATION => {
139                    crate::interface::slack::block_actions::edit_button::handle(
140                        self,
141                        block_actions,
142                        action,
143                    )
144                    .await?
145                }
146                ACTION_CANCEL_RESERVATION => {
147                    crate::interface::slack::block_actions::cancel_button::handle(
148                        self,
149                        block_actions,
150                        action,
151                    )
152                    .await?
153                }
154                _ => {}
155            }
156        }
157
158        Ok(())
159    }
160
161    /// モーダル内のインタラクションをルーティング(リソースタイプ変更、サーバー選択など)
162    async fn route_modal_interactions(
163        &self,
164        block_actions: &SlackInteractionBlockActionsEvent,
165    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
166        let Some(actions) = &block_actions.actions else {
167            return Ok(());
168        };
169
170        for action in actions {
171            let action_id = action.action_id.to_string();
172
173            match action_id.as_str() {
174                ACTION_RESERVE_RESOURCE_TYPE | ACTION_RESERVE_SERVER_SELECT => {
175                    crate::interface::slack::block_actions::modal_state_change::handle(
176                        self,
177                        block_actions,
178                        action,
179                    )
180                    .await?
181                }
182                _ => {
183                    // その他のモーダルアクションは送信時に処理
184                }
185            }
186        }
187
188        Ok(())
189    }
190}