Skip to main content

tetratto_core/database/
guest_logs.rs

1use oiseau::cache::Cache;
2use crate::model::{
3    Error, Result,
4    requests::{ActionRequest, ActionType},
5    auth::{Notification, User},
6    guest_logs::GuestLog,
7    permissions::{SecondaryPermission, FinePermission},
8};
9use crate::{auto_method, DataManager};
10use oiseau::{execute, get, query_rows, params, PostgresRow};
11
12impl DataManager {
13    /// Get a [`GuestLog`] from an SQL row.
14    pub(crate) fn get_guest_log_from_row(x: &PostgresRow) -> GuestLog {
15        GuestLog {
16            id: get!(x->0(i64)) as usize,
17            created: get!(x->1(i64)) as usize,
18            owner: get!(x->2(i64)) as usize,
19            name: get!(x->3(String)),
20            content: get!(x->4(String)),
21            waiting_for_review: get!(x->5(i32)) as i8 == 1,
22            ip: get!(x->6(String)),
23        }
24    }
25
26    auto_method!(get_guest_log_by_id()@get_guest_log_from_row -> "SELECT * FROM guest_logs WHERE id = $1" --name="guest_log" --returns=GuestLog --cache-key-tmpl="atto.guest_log:{}");
27
28    /// Get all guest_logs by `owner`.
29    pub async fn get_guest_logs_by_owner(
30        &self,
31        owner: usize,
32        batch: usize,
33        page: usize,
34    ) -> Result<Vec<GuestLog>> {
35        let conn = match self.0.connect().await {
36            Ok(c) => c,
37            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
38        };
39
40        let res = query_rows!(
41            &conn,
42            "SELECT * FROM guest_logs WHERE owner = $1 AND waiting_for_review = 0 ORDER BY created DESC LIMIT $2 OFFSET $3",
43            &[&(owner as i64), &(batch as i64), &((page * batch) as i64)],
44            |x| { Self::get_guest_log_from_row(x) }
45        );
46
47        if res.is_err() {
48            return Err(Error::GeneralNotFound("guest_log".to_string()));
49        }
50
51        Ok(res.unwrap())
52    }
53
54    /// Get all guest_logs by `owner`.
55    pub async fn get_guest_logs_by_owner_wfr(
56        &self,
57        owner: usize,
58        batch: usize,
59        page: usize,
60    ) -> Result<Vec<GuestLog>> {
61        let conn = match self.0.connect().await {
62            Ok(c) => c,
63            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
64        };
65
66        let res = query_rows!(
67            &conn,
68            "SELECT * FROM guest_logs WHERE owner = $1 AND waiting_for_review = 1 ORDER BY created DESC LIMIT $2 OFFSET $3",
69            &[&(owner as i64), &(batch as i64), &((page * batch) as i64)],
70            |x| { Self::get_guest_log_from_row(x) }
71        );
72
73        if res.is_err() {
74            return Err(Error::GeneralNotFound("guest_log".to_string()));
75        }
76
77        Ok(res.unwrap())
78    }
79
80    /// Create a new guest_log in the database.
81    ///
82    /// # Arguments
83    /// * `data` - a mock [`GuestLog`] object to insert
84    pub async fn create_guest_log(&self, data: GuestLog) -> Result<usize> {
85        // check lengths
86        if data.name.len() < 2 {
87            return Err(Error::DataTooShort("name".to_string()));
88        }
89
90        if data.name.len() > 32 {
91            return Err(Error::DataTooLong("name".to_string()));
92        }
93
94        if data.content.len() < 2 {
95            return Err(Error::DataTooShort("content".to_string()));
96        }
97
98        if data.content.len() > 2048 {
99            return Err(Error::DataTooLong("content".to_string()));
100        }
101
102        // ...
103        let owner = self.get_user_by_id(data.owner).await?;
104
105        let conn = match self.0.connect().await {
106            Ok(c) => c,
107            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
108        };
109
110        let res = execute!(
111            &conn,
112            "INSERT INTO guest_logs VALUES ($1, $2, $3, $4, $5, $6, $7)",
113            params![
114                &(data.id as i64),
115                &(data.created as i64),
116                &(data.owner as i64),
117                &data.name,
118                &data.content,
119                &{ if data.waiting_for_review { 1 } else { 0 } },
120                &data.ip,
121            ]
122        );
123
124        if let Err(e) = res {
125            return Err(Error::DatabaseError(e.to_string()));
126        }
127
128        // create request
129        if data.waiting_for_review {
130            self.create_request(ActionRequest::with_id(
131                data.owner,
132                data.owner,
133                ActionType::GuestLog,
134                data.id,
135                None,
136            ))
137            .await?;
138        } else {
139            // send notification
140            self.create_notification(Notification::new(
141                "New message on your guestbook!".to_string(),
142                format!(
143                    "You've received a new message in your [guestbook](/@{}/guestbook).",
144                    owner.username
145                ),
146                data.owner,
147            ))
148            .await?;
149        }
150
151        // return
152        Ok(data.id)
153    }
154
155    pub async fn delete_guest_log(&self, id: usize, user: &User) -> Result<()> {
156        let y = self.get_guest_log_by_id(id).await?;
157
158        if user.id != y.owner
159            && !user
160                .secondary_permissions
161                .check(SecondaryPermission::MANAGE_GUEST_LOGS)
162        {
163            return Err(Error::NotAllowed);
164        }
165
166        let conn = match self.0.connect().await {
167            Ok(c) => c,
168            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
169        };
170
171        let res = execute!(
172            &conn,
173            "DELETE FROM guest_logs WHERE id = $1",
174            &[&(id as i64)]
175        );
176
177        if let Err(e) = res {
178            return Err(Error::DatabaseError(e.to_string()));
179        }
180
181        self.0.1.remove(format!("atto.guest_log:{}", id)).await;
182
183        // delete request (if it exists and guest_log isn't global)
184        if y.waiting_for_review
185            && self
186                .get_request_by_id_linked_asset(y.owner, y.id)
187                .await
188                .is_ok()
189        {
190            self.delete_request(y.owner, y.id, user, false).await?;
191        }
192
193        // return
194        Ok(())
195    }
196
197    pub async fn update_guest_log_waiting_for_review(
198        &self,
199        id: usize,
200        new_wfr: bool,
201        user: &User,
202    ) -> Result<()> {
203        let y = self.get_guest_log_by_id(id).await?;
204
205        if y.owner != user.id && !user.permissions.check(FinePermission::MANAGE_REQUESTS) {
206            return Err(Error::NotAllowed);
207        }
208
209        // ...
210        let conn = match self.0.connect().await {
211            Ok(c) => c,
212            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
213        };
214
215        let res = execute!(
216            &conn,
217            "UPDATE guest_logs SET waiting_for_review = $1 WHERE id = $2",
218            params![&{ if new_wfr { 1 } else { 0 } }, &(id as i64)]
219        );
220
221        if let Err(e) = res {
222            return Err(Error::DatabaseError(e.to_string()));
223        }
224
225        self.0.1.remove(format!("atto.guest_log:{}", id)).await;
226        Ok(())
227    }
228}