opentalk_roomserver_room/storage/
controller_module_storage.rs1use anyhow::Context;
5use async_trait::async_trait;
6use opentalk_roomserver_signaling::storage::module_resources::{
7 Error, provider::ModuleResourceProvider,
8};
9use opentalk_service_auth::ApiKey;
10use opentalk_types_api_internal::module_resources::{
11 ModuleResource, ModuleResourceFilter, ModuleResourceOperation, NewModuleResource,
12 PatchModuleResourceBody,
13};
14use reqwest::{Response, header::CONTENT_TYPE};
15use serde::de::DeserializeOwned;
16use url::Url;
17
18const MAX_BODY_PREVIEW_LENGTH: usize = 80;
19
20#[derive(Debug)]
21pub struct ControllerModuleStorage {
22 base_url: Url,
23 api_key: ApiKey,
24 client: reqwest::Client,
25}
26
27impl ControllerModuleStorage {
28 pub fn new(base_url: Url, api_key: ApiKey) -> Self {
29 Self {
30 base_url,
31 api_key,
32 client: reqwest::Client::new(),
33 }
34 }
35
36 fn module_resource_endpoint(&self) -> anyhow::Result<Url> {
37 self.base_url
38 .join("/internal/module_resources")
39 .context("Invalid URL")
40 }
41}
42
43#[async_trait]
44impl ModuleResourceProvider for ControllerModuleStorage {
45 async fn create(&self, resource: NewModuleResource) -> Result<ModuleResource, Error> {
46 let url = self.module_resource_endpoint()?;
47
48 let new_resource_body =
49 serde_json::to_vec(&resource).context("Failed to serialize POST request body")?;
50
51 let token = self
52 .api_key
53 .generate_jwt()
54 .context("Failed to generate auth header")?;
55
56 let response = self
57 .client
58 .post(url)
59 .body(new_resource_body)
60 .header(CONTENT_TYPE, "application/json")
61 .bearer_auth(token)
62 .send()
63 .await
64 .context("Failed to send POST module resource request")?;
65
66 let module_resource = parse_response_body(response)
67 .await
68 .context("Failed to create module resource")?;
69
70 Ok(module_resource)
71 }
72
73 async fn get(&self, filter: ModuleResourceFilter) -> Result<Vec<ModuleResource>, Error> {
74 let url = self.module_resource_endpoint()?;
75
76 let request_body =
77 serde_json::to_vec(&filter).context("Failed to serialize GET request body")?;
78
79 let token = self
80 .api_key
81 .generate_jwt()
82 .context("Failed to generate auth header")?;
83
84 let response = self
85 .client
86 .get(url)
87 .body(request_body)
88 .header(CONTENT_TYPE, "application/json")
89 .bearer_auth(token)
90 .send()
91 .await
92 .context("Failed to send GET module resource request")?;
93
94 let module_resources = parse_response_body(response)
95 .await
96 .context("Failed to get module resource")?;
97
98 Ok(module_resources)
99 }
100
101 async fn patch(
102 &self,
103 filter: ModuleResourceFilter,
104 operations: Vec<ModuleResourceOperation>,
105 ) -> Result<Vec<ModuleResource>, Error> {
106 let url = self.module_resource_endpoint()?;
107
108 let patch_request = PatchModuleResourceBody {
109 filter,
110 patch_operations: operations,
111 };
112
113 let request_body =
114 serde_json::to_vec(&patch_request).context("Failed to serialize PATCH request body")?;
115
116 let token = self
117 .api_key
118 .generate_jwt()
119 .context("Failed to generate auth header")?;
120
121 let response = self
122 .client
123 .patch(url)
124 .body(request_body)
125 .header(CONTENT_TYPE, "application/json")
126 .bearer_auth(token)
127 .send()
128 .await
129 .context("Failed to send PATCH module resource request")?;
130
131 let module_resources = parse_response_body(response)
132 .await
133 .context("Failed to update module resource")?;
134
135 Ok(module_resources)
136 }
137
138 async fn delete(&self, filter: ModuleResourceFilter) -> Result<Vec<ModuleResource>, Error> {
139 let url = self.module_resource_endpoint()?;
140
141 let request_body =
142 serde_json::to_vec(&filter).context("Failed to serialize DELETE request body")?;
143
144 let token = self
145 .api_key
146 .generate_jwt()
147 .context("Failed to generate auth header")?;
148
149 let response = self
150 .client
151 .delete(url)
152 .body(request_body)
153 .header(CONTENT_TYPE, "application/json")
154 .bearer_auth(token)
155 .send()
156 .await
157 .context("Failed to send DELETE module resource request")?;
158
159 let deleted_resources = parse_response_body(response).await?;
160
161 Ok(deleted_resources)
162 }
163}
164
165async fn parse_response_body<T: DeserializeOwned>(response: Response) -> anyhow::Result<T> {
166 let status = response.status();
167 let body = response
168 .bytes()
169 .await
170 .context("Failed to read response body")?;
171
172 if !status.is_success() {
173 let error_body = match std::str::from_utf8(&body) {
174 Ok(s) if s.len() > MAX_BODY_PREVIEW_LENGTH => {
175 format!("{}...[truncated]", &s[..MAX_BODY_PREVIEW_LENGTH])
176 }
177 Ok(s) => s.to_string(),
178 Err(_) => "<non-utf8>".to_string(),
179 };
180
181 return Err(anyhow::anyhow!(
182 "Received non-success response with status: `{}` Body: `{}`",
183 status,
184 error_body,
185 ));
186 }
187
188 let success_body: T = serde_json::from_slice(&body).with_context(|| {
189 format!(
190 "Failed to parse upload response body: {:?}",
191 std::str::from_utf8(&body).unwrap_or("<non-utf8>"),
192 )
193 })?;
194
195 Ok(success_body)
196}