Skip to main content

opentalk_roomserver_room/storage/
controller_module_storage.rs

1// SPDX-License-Identifier: EUPL-1.2
2// SPDX-FileCopyrightText: OpenTalk Team <mail@opentalk.eu>
3
4use 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}