opentalk_nextcloud_client/
share_updater.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5use std::collections::HashSet;
6
7use chrono::NaiveDate;
8use log::warn;
9use reqwest::StatusCode;
10use serde::Serialize;
11
12use crate::{
13    types::{OcsShareAnswer, OcsShareData, ShareAnswer},
14    Client, Error, Result, ShareId, SharePermission,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18#[serde(rename_all = "camelCase")]
19enum ParameterUpdate {
20    PublicUpload(bool),
21    Permissions(#[serde(with = "crate::utils::share_permissions")] HashSet<SharePermission>),
22    ExpireDate(String),
23    Note(String),
24    Label(String),
25}
26
27#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
28#[serde(rename_all = "camelCase")]
29struct Parameters {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    public_upload: Option<bool>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    password: Option<String>,
34    #[serde(
35        with = "crate::utils::optional_share_permissions",
36        skip_serializing_if = "Option::is_none"
37    )]
38    permissions: Option<HashSet<SharePermission>>,
39    #[serde(
40        with = "crate::utils::optional_naive_date",
41        skip_serializing_if = "Option::is_none"
42    )]
43    expire_date: Option<NaiveDate>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    note: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    label: Option<String>,
48}
49
50#[must_use]
51pub struct ShareUpdater {
52    client: Client,
53    share_id: ShareId,
54}
55
56impl ShareUpdater {
57    pub(crate) fn new(client: Client, share_id: ShareId) -> Self {
58        Self { client, share_id }
59    }
60
61    pub async fn public_upload(self, public_upload: bool) -> Result<OcsShareAnswer<OcsShareData>> {
62        self.send(ParameterUpdate::PublicUpload(public_upload))
63            .await
64    }
65
66    pub async fn permissions(
67        self,
68        permissions: HashSet<SharePermission>,
69    ) -> Result<OcsShareAnswer<OcsShareData>> {
70        self.send(ParameterUpdate::Permissions(permissions)).await
71    }
72
73    pub async fn expire_date(
74        self,
75        expire_date: Option<NaiveDate>,
76    ) -> Result<OcsShareAnswer<OcsShareData>> {
77        self.send(ParameterUpdate::ExpireDate(
78            expire_date
79                .map(|date| date.format("%Y-%m-%d").to_string())
80                .unwrap_or_default(),
81        ))
82        .await
83    }
84
85    pub async fn note<N: Into<String>>(self, note: N) -> Result<OcsShareAnswer<OcsShareData>> {
86        self.send(ParameterUpdate::Note(note.into())).await
87    }
88
89    pub async fn label<L: Into<String>>(self, label: L) -> Result<OcsShareAnswer<OcsShareData>> {
90        self.send(ParameterUpdate::Label(label.into())).await
91    }
92
93    async fn send(self, parameter: ParameterUpdate) -> Result<OcsShareAnswer<OcsShareData>> {
94        let Self { client, share_id } = self;
95
96        let url = client
97            .share_api_base_url()?
98            .join("shares/")?
99            .join(share_id.as_str())?;
100        let request = client
101            .inner
102            .http_client
103            .put(url)
104            .basic_auth(&client.inner.username, Some(&client.inner.password))
105            .json(&parameter);
106        let answer = request.send().await?;
107
108        match answer.status() {
109            StatusCode::CONTINUE | StatusCode::OK => {}
110            StatusCode::BAD_REQUEST => {
111                // 400
112                return Err(Error::WrongParameter);
113            }
114            StatusCode::UNAUTHORIZED => {
115                // 401
116                return Err(Error::Unauthorized);
117            }
118            StatusCode::FORBIDDEN => {
119                // 403
120                return Err(Error::PublicUploadDisabledByAdmin);
121            }
122            StatusCode::NOT_FOUND => {
123                // 404
124                return Err(Error::ShareNotFound { share_id });
125            }
126            status_code => {
127                warn!("Received unexpected status code {status_code} from NextCloud server.");
128                match answer.text().await {
129                    Ok(text) => {
130                        warn!("Response for unexpected status code {status_code}:\n{text}");
131                    }
132                    Err(e) => {
133                        warn!("Error retrieving body from NextCloud: {}", e);
134                    }
135                }
136                return Err(Error::UnexpectedStatusCode { status_code });
137            }
138        }
139        let answer: ShareAnswer<OcsShareData> = answer.json().await?;
140        Ok(answer.ocs)
141    }
142}