1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
5
6use crate::handlers::ChatbotService;
7use crate::state::ChatbotState;
8use crate::types::{ChimeConfig, SlackConfig, TeamsConfig};
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct ChatbotStateView {
12 #[serde(default)]
13 pub slack_configs: HashMap<String, SlackConfigView>,
14 #[serde(default)]
15 pub chime_configs: HashMap<String, ChimeConfigView>,
16 #[serde(default)]
17 pub teams_configs: HashMap<String, TeamsConfigView>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SlackConfigView {
22 pub arn: String,
23 pub configuration_name: String,
24 pub slack_team_id: String,
25 pub slack_channel_id: String,
26 pub slack_channel_name: Option<String>,
27 pub iam_role_arn: String,
28 pub sns_topic_arns: Vec<String>,
29 pub logging_level: Option<String>,
30 pub guardrail_policy_arns: Vec<String>,
31 pub user_authorization_required: Option<bool>,
32 pub tags: HashMap<String, String>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ChimeConfigView {
37 pub arn: String,
38 pub configuration_name: String,
39 pub webhook_url: String,
40 pub webhook_description: String,
41 pub iam_role_arn: String,
42 pub sns_topic_arns: Vec<String>,
43 pub logging_level: Option<String>,
44 pub tags: HashMap<String, String>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TeamsConfigView {
49 pub arn: String,
50 pub configuration_name: String,
51 pub team_id: String,
52 pub team_name: Option<String>,
53 pub tenant_id: String,
54 pub channel_id: String,
55 pub channel_name: Option<String>,
56 pub iam_role_arn: String,
57 pub sns_topic_arns: Vec<String>,
58 pub logging_level: Option<String>,
59 pub guardrail_policy_arns: Vec<String>,
60 pub user_authorization_required: Option<bool>,
61 pub tags: HashMap<String, String>,
62}
63
64impl From<&SlackConfig> for SlackConfigView {
65 fn from(c: &SlackConfig) -> Self {
66 Self {
67 arn: c.arn.clone(),
68 configuration_name: c.configuration_name.clone(),
69 slack_team_id: c.slack_team_id.clone(),
70 slack_channel_id: c.slack_channel_id.clone(),
71 slack_channel_name: c.slack_channel_name.clone(),
72 iam_role_arn: c.iam_role_arn.clone(),
73 sns_topic_arns: c.sns_topic_arns.clone(),
74 logging_level: c.logging_level.clone(),
75 guardrail_policy_arns: c.guardrail_policy_arns.clone(),
76 user_authorization_required: c.user_authorization_required,
77 tags: c.tags.clone(),
78 }
79 }
80}
81
82impl From<SlackConfigView> for SlackConfig {
83 fn from(v: SlackConfigView) -> Self {
84 Self {
85 arn: v.arn,
86 configuration_name: v.configuration_name,
87 slack_team_id: v.slack_team_id,
88 slack_channel_id: v.slack_channel_id,
89 slack_channel_name: v.slack_channel_name,
90 iam_role_arn: v.iam_role_arn,
91 sns_topic_arns: v.sns_topic_arns,
92 logging_level: v.logging_level,
93 guardrail_policy_arns: v.guardrail_policy_arns,
94 user_authorization_required: v.user_authorization_required,
95 tags: v.tags,
96 }
97 }
98}
99
100impl From<&ChimeConfig> for ChimeConfigView {
101 fn from(c: &ChimeConfig) -> Self {
102 Self {
103 arn: c.arn.clone(),
104 configuration_name: c.configuration_name.clone(),
105 webhook_url: c.webhook_url.clone(),
106 webhook_description: c.webhook_description.clone(),
107 iam_role_arn: c.iam_role_arn.clone(),
108 sns_topic_arns: c.sns_topic_arns.clone(),
109 logging_level: c.logging_level.clone(),
110 tags: c.tags.clone(),
111 }
112 }
113}
114
115impl From<ChimeConfigView> for ChimeConfig {
116 fn from(v: ChimeConfigView) -> Self {
117 Self {
118 arn: v.arn,
119 configuration_name: v.configuration_name,
120 webhook_url: v.webhook_url,
121 webhook_description: v.webhook_description,
122 iam_role_arn: v.iam_role_arn,
123 sns_topic_arns: v.sns_topic_arns,
124 logging_level: v.logging_level,
125 tags: v.tags,
126 }
127 }
128}
129
130impl From<&TeamsConfig> for TeamsConfigView {
131 fn from(c: &TeamsConfig) -> Self {
132 Self {
133 arn: c.arn.clone(),
134 configuration_name: c.configuration_name.clone(),
135 team_id: c.team_id.clone(),
136 team_name: c.team_name.clone(),
137 tenant_id: c.tenant_id.clone(),
138 channel_id: c.channel_id.clone(),
139 channel_name: c.channel_name.clone(),
140 iam_role_arn: c.iam_role_arn.clone(),
141 sns_topic_arns: c.sns_topic_arns.clone(),
142 logging_level: c.logging_level.clone(),
143 guardrail_policy_arns: c.guardrail_policy_arns.clone(),
144 user_authorization_required: c.user_authorization_required,
145 tags: c.tags.clone(),
146 }
147 }
148}
149
150impl From<TeamsConfigView> for TeamsConfig {
151 fn from(v: TeamsConfigView) -> Self {
152 Self {
153 arn: v.arn,
154 configuration_name: v.configuration_name,
155 team_id: v.team_id,
156 team_name: v.team_name,
157 tenant_id: v.tenant_id,
158 channel_id: v.channel_id,
159 channel_name: v.channel_name,
160 iam_role_arn: v.iam_role_arn,
161 sns_topic_arns: v.sns_topic_arns,
162 logging_level: v.logging_level,
163 guardrail_policy_arns: v.guardrail_policy_arns,
164 user_authorization_required: v.user_authorization_required,
165 tags: v.tags,
166 }
167 }
168}
169
170impl From<&ChatbotState> for ChatbotStateView {
171 fn from(state: &ChatbotState) -> Self {
172 Self {
173 slack_configs: state
174 .slack_configs
175 .iter()
176 .map(|(k, v)| (k.clone(), SlackConfigView::from(v)))
177 .collect(),
178 chime_configs: state
179 .chime_configs
180 .iter()
181 .map(|(k, v)| (k.clone(), ChimeConfigView::from(v)))
182 .collect(),
183 teams_configs: state
184 .teams_configs
185 .iter()
186 .map(|(k, v)| (k.clone(), TeamsConfigView::from(v)))
187 .collect(),
188 }
189 }
190}
191
192impl From<ChatbotStateView> for ChatbotState {
193 fn from(view: ChatbotStateView) -> Self {
194 Self {
195 slack_configs: view
196 .slack_configs
197 .into_iter()
198 .map(|(k, v)| (k, SlackConfig::from(v)))
199 .collect(),
200 chime_configs: view
201 .chime_configs
202 .into_iter()
203 .map(|(k, v)| (k, ChimeConfig::from(v)))
204 .collect(),
205 teams_configs: view
206 .teams_configs
207 .into_iter()
208 .map(|(k, v)| (k, TeamsConfig::from(v)))
209 .collect(),
210 }
211 }
212}
213
214impl StatefulService for ChatbotService {
215 type StateView = ChatbotStateView;
216
217 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
218 let state = self.state.get(account_id, region);
219 let guard = state.read().await;
220 ChatbotStateView::from(&*guard)
221 }
222
223 async fn restore(
224 &self,
225 account_id: &str,
226 region: &str,
227 view: Self::StateView,
228 ) -> Result<(), StateViewError> {
229 let state = self.state.get(account_id, region);
230 {
231 let mut guard = state.write().await;
232 *guard = ChatbotState::from(view);
233 }
234 self.notify_state_changed(account_id, region).await;
235 Ok(())
236 }
237
238 async fn merge(
239 &self,
240 account_id: &str,
241 region: &str,
242 view: Self::StateView,
243 ) -> Result<(), StateViewError> {
244 let state = self.state.get(account_id, region);
245 {
246 let mut guard = state.write().await;
247 for (k, v) in view.slack_configs {
248 guard.slack_configs.insert(k, SlackConfig::from(v));
249 }
250 for (k, v) in view.chime_configs {
251 guard.chime_configs.insert(k, ChimeConfig::from(v));
252 }
253 for (k, v) in view.teams_configs {
254 guard.teams_configs.insert(k, TeamsConfig::from(v));
255 }
256 }
257 self.notify_state_changed(account_id, region).await;
258 Ok(())
259 }
260
261 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
262 &self.notifier
263 }
264}