tetratto_core/database/
common.rs

1use crate::model::{Error, Result};
2use super::{DataManager, drivers::common};
3use oiseau::{cache::Cache, execute, query_row, params};
4
5pub const NAME_REGEX: &str = r"[^\w_\-\.,!]+";
6
7impl DataManager {
8    pub async fn init(&self) -> Result<()> {
9        let conn = match self.0.connect().await {
10            Ok(c) => c,
11            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
12        };
13
14        execute!(&conn, common::CREATE_TABLE_USERS).unwrap();
15        execute!(&conn, common::CREATE_TABLE_COMMUNITIES).unwrap();
16        execute!(&conn, common::CREATE_TABLE_POSTS).unwrap();
17        execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS).unwrap();
18        execute!(&conn, common::CREATE_TABLE_REACTIONS).unwrap();
19        execute!(&conn, common::CREATE_TABLE_NOTIFICATIONS).unwrap();
20        execute!(&conn, common::CREATE_TABLE_USERFOLLOWS).unwrap();
21        execute!(&conn, common::CREATE_TABLE_USERBLOCKS).unwrap();
22        execute!(&conn, common::CREATE_TABLE_IPBANS).unwrap();
23        execute!(&conn, common::CREATE_TABLE_AUDIT_LOG).unwrap();
24        execute!(&conn, common::CREATE_TABLE_REPORTS).unwrap();
25        execute!(&conn, common::CREATE_TABLE_USER_WARNINGS).unwrap();
26        execute!(&conn, common::CREATE_TABLE_REQUESTS).unwrap();
27        execute!(&conn, common::CREATE_TABLE_QUESTIONS).unwrap();
28        execute!(&conn, common::CREATE_TABLE_IPBLOCKS).unwrap();
29        execute!(&conn, common::CREATE_TABLE_CHANNELS).unwrap();
30        execute!(&conn, common::CREATE_TABLE_MESSAGES).unwrap();
31        execute!(&conn, common::CREATE_TABLE_UPLOADS).unwrap();
32        execute!(&conn, common::CREATE_TABLE_EMOJIS).unwrap();
33        execute!(&conn, common::CREATE_TABLE_STACKS).unwrap();
34        execute!(&conn, common::CREATE_TABLE_DRAFTS).unwrap();
35        execute!(&conn, common::CREATE_TABLE_POLLS).unwrap();
36        execute!(&conn, common::CREATE_TABLE_POLLVOTES).unwrap();
37        execute!(&conn, common::CREATE_TABLE_APPS).unwrap();
38        execute!(&conn, common::CREATE_TABLE_STACKBLOCKS).unwrap();
39        execute!(&conn, common::CREATE_TABLE_JOURNALS).unwrap();
40        execute!(&conn, common::CREATE_TABLE_NOTES).unwrap();
41        execute!(&conn, common::CREATE_TABLE_MESSAGE_REACTIONS).unwrap();
42        execute!(&conn, common::CREATE_TABLE_INVITE_CODES).unwrap();
43        execute!(&conn, common::CREATE_TABLE_DOMAINS).unwrap();
44        execute!(&conn, common::CREATE_TABLE_SERVICES).unwrap();
45        execute!(&conn, common::CREATE_TABLE_APP_DATA).unwrap();
46        execute!(&conn, common::CREATE_TABLE_LETTERS).unwrap();
47        execute!(&conn, common::CREATE_TABLE_TRANSFERS).unwrap();
48        execute!(&conn, common::CREATE_TABLE_PRODUCTS).unwrap();
49        execute!(&conn, common::CREATE_TABLE_ADS).unwrap();
50
51        for x in common::VERSION_MIGRATIONS.split(";") {
52            execute!(&conn, x).unwrap();
53        }
54
55        self.0
56            .1
57            .set("atto.active_connections:users".to_string(), "0".to_string())
58            .await;
59        self.0
60            .1
61            .set("atto.active_connections:chats".to_string(), "0".to_string())
62            .await;
63
64        Ok(())
65    }
66
67    pub async fn get_table_row_count(&self, table: &str) -> Result<i32> {
68        let conn = match self.0.connect().await {
69            Ok(c) => c,
70            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
71        };
72
73        let res = query_row!(
74            &conn,
75            &format!("SELECT COUNT(*)::int FROM {}", table),
76            params![],
77            |x| Ok(x.get::<usize, i32>(0))
78        );
79
80        if let Err(e) = res {
81            return Err(Error::DatabaseError(e.to_string()));
82        }
83
84        Ok(res.unwrap())
85    }
86
87    pub async fn get_table_row_count_where(&self, table: &str, r#where: &str) -> Result<i32> {
88        let conn = match self.0.connect().await {
89            Ok(c) => c,
90            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
91        };
92
93        let res = query_row!(
94            &conn,
95            &format!("SELECT COUNT(*)::int FROM {} WHERE {}", table, r#where),
96            params![],
97            |x| Ok(x.get::<usize, i32>(0))
98        );
99
100        if let Err(e) = res {
101            return Err(Error::DatabaseError(e.to_string()));
102        }
103
104        Ok(res.unwrap())
105    }
106}
107
108#[macro_export]
109macro_rules! auto_method {
110    ($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => {
111        pub async fn $name(&self, id: usize) -> Result<$returns_> {
112            let conn = match self.0.connect().await {
113                Ok(c) => c,
114                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
115            };
116
117            let res = query_row!(&conn, $query, &[&(id as i64)], |x| {
118                Ok(Self::$select_fn(x))
119            });
120
121            if res.is_err() {
122                return Err(Error::GeneralNotFound($name_.to_string()));
123            }
124
125            Ok(res.unwrap())
126        }
127    };
128
129    ($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
130        pub async fn $name(&self, id: usize) -> Result<$returns_> {
131            if let Some(cached) = self.0.1.get(format!($cache_key_tmpl, id)).await {
132                if let Ok(c) = serde_json::from_str(&cached) {
133                    return Ok(c);
134                }
135            }
136
137            let conn = match self.0.connect().await {
138                Ok(c) => c,
139                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
140            };
141
142            let res = oiseau::query_row!(&conn, $query, &[&(id as i64)], |x| {
143                Ok(Self::$select_fn(x))
144            });
145
146            if res.is_err() {
147                return Err(Error::GeneralNotFound($name_.to_string()));
148            }
149
150            let x = res.unwrap();
151            self.0
152                .1
153                .set(
154                    format!($cache_key_tmpl, id),
155                    serde_json::to_string(&x).unwrap(),
156                )
157                .await;
158
159            Ok(x)
160        }
161    };
162
163    ($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => {
164        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
165            let conn = match self.0.connect().await {
166                Ok(c) => c,
167                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
168            };
169
170            let res =
171                oiseau::query_row!(&conn, $query, &[&selector], |x| { Ok(Self::$select_fn(x)) });
172
173            if res.is_err() {
174                return Err(Error::GeneralNotFound($name_.to_string()));
175            }
176
177            Ok(res.unwrap())
178        }
179    };
180
181    ($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
182        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
183            let selector = selector.to_string().to_lowercase();
184
185            if let Some(cached) = self.0.1.get(format!($cache_key_tmpl, selector)).await {
186                return Ok(serde_json::from_str(&cached).unwrap());
187            }
188
189            let conn = match self.0.connect().await {
190                Ok(c) => c,
191                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
192            };
193
194            let res = query_row!(&conn, $query, &[&selector.to_string()], |x| {
195                Ok(Self::$select_fn(x))
196            });
197
198            if res.is_err() {
199                return Err(Error::GeneralNotFound($name_.to_string()));
200            }
201
202            let x = res.unwrap();
203            self.0
204                .1
205                .set(
206                    format!($cache_key_tmpl, selector),
207                    serde_json::to_string(&x).unwrap(),
208                )
209                .await;
210
211            Ok(x)
212        }
213    };
214
215    ($name:ident($selector_t:ty as i64)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
216        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
217            if let Some(cached) = self
218                .0
219                .1
220                .get(format!($cache_key_tmpl, selector.to_string()))
221                .await
222            {
223                match serde_json::from_str(&cached) {
224                    Ok(x) => return Ok(x),
225                    Err(_) => {
226                        self.0
227                            .1
228                            .remove(format!($cache_key_tmpl, selector.to_string()))
229                            .await
230                    }
231                };
232            }
233
234            let conn = match self.0.connect().await {
235                Ok(c) => c,
236                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
237            };
238
239            let res = oiseau::query_row!(&conn, $query, &[&(selector as i64)], |x| {
240                Ok(Self::$select_fn(x))
241            });
242
243            if res.is_err() {
244                return Err(Error::GeneralNotFound($name_.to_string()));
245            }
246
247            let x = res.unwrap();
248            self.0
249                .1
250                .set(
251                    format!($cache_key_tmpl, selector),
252                    serde_json::to_string(&x).unwrap(),
253                )
254                .await;
255
256            Ok(x)
257        }
258    };
259
260    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal) => {
261        pub async fn $name(&self, id: usize, user: &User) -> Result<()> {
262            let y = self.$select_fn(id).await?;
263
264            if user.id != y.owner {
265                if !user.permissions.check($permission) {
266                    return Err(Error::NotAllowed);
267                } else {
268                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
269                        user.id,
270                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
271                    ))
272                }
273            }
274
275            let conn = match self.0.connect().await {
276                Ok(c) => c,
277                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
278            };
279
280            let res = execute!(&conn, $query, &[&(id as i64)]);
281
282            if let Err(e) = res {
283                return Err(Error::DatabaseError(e.to_string()));
284            }
285
286            Ok(())
287        }
288    };
289
290    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
291        pub async fn $name(&self, id: usize, user: &User) -> Result<()> {
292            let y = self.$select_fn(id).await?;
293
294            if user.id != y.owner {
295                if !user.permissions.check($permission) {
296                    return Err(Error::NotAllowed);
297                } else {
298                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
299                        user.id,
300                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
301                    ))
302                }
303            }
304
305            let conn = match self.0.connect().await {
306                Ok(c) => c,
307                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
308            };
309
310            let res = execute!(&conn, $query, &[&(id as i64)]);
311
312            if let Err(e) = res {
313                return Err(Error::DatabaseError(e.to_string()));
314            }
315
316            self.0.1.remove(format!($cache_key_tmpl, id)).await;
317
318            Ok(())
319        }
320    };
321
322    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal) => {
323        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
324            let y = self.$select_fn(id).await?;
325
326            if user.id != y.owner {
327                if !user.permissions.check($permission) {
328                    return Err(Error::NotAllowed);
329                } else {
330                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
331                        user.id,
332                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
333                    ))
334                    .await?
335                }
336            }
337
338            let conn = match self.0.connect().await {
339                Ok(c) => c,
340                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
341            };
342
343            let res = execute!(&conn, $query, &[&x, &(id as i64)]);
344
345            if let Err(e) = res {
346                return Err(Error::DatabaseError(e.to_string()));
347            }
348
349            Ok(())
350        }
351    };
352
353    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
354        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
355            let y = self.$select_fn(id).await?;
356
357            if user.id != y.owner {
358                if !user.permissions.check($permission) {
359                    return Err(Error::NotAllowed);
360                } else {
361                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
362                        user.id,
363                        format!("invoked `{}` with x value `{x}`", stringify!($name)),
364                    ))
365                    .await?
366                }
367            }
368
369            let conn = match self.0.connect().await {
370                Ok(c) => c,
371                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
372            };
373
374            let res = execute!(&conn, $query, params![&x, &(id as i64)]);
375
376            if let Err(e) = res {
377                return Err(Error::DatabaseError(e.to_string()));
378            }
379
380            self.0.1.remove(format!($cache_key_tmpl, id)).await;
381
382            Ok(())
383        }
384    };
385
386    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde) => {
387        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
388            let y = self.$select_fn(id).await?;
389
390            if user.id != y.owner {
391                if !user.permissions.check($permission) {
392                    return Err(Error::NotAllowed);
393                } else {
394                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
395                        user.id,
396                        format!("invoked `{}` with x value `{id}`", stringify!($name), id),
397                    ))
398                    .await?
399                }
400            }
401
402            let conn = match self.0.connect().await {
403                Ok(c) => c,
404                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
405            };
406
407            let res = execute!(
408                &conn,
409                $query,
410                &[&serde_json::to_string(&x).unwrap(), &(id as i64)]
411            );
412
413            if let Err(e) = res {
414                return Err(Error::DatabaseError(e.to_string()));
415            }
416
417            Ok(())
418        }
419    };
420
421    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
422        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
423            let y = self.$select_fn(id).await?;
424
425            if user.id != y.owner {
426                if !user.permissions.check($permission) {
427                    return Err(Error::NotAllowed);
428                } else {
429                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
430                        user.id,
431                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
432                    ))
433                    .await?
434                }
435            }
436
437            let conn = match self.0.connect().await {
438                Ok(c) => c,
439                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
440            };
441
442            let res = execute!(
443                &conn,
444                $query,
445                params![&serde_json::to_string(&x).unwrap(), &(id as i64)]
446            );
447
448            if let Err(e) = res {
449                return Err(Error::DatabaseError(e.to_string()));
450            }
451
452            self.0.1.remove(format!($cache_key_tmpl, id)).await;
453
454            Ok(())
455        }
456    };
457
458    ($name:ident($x:ty) -> $query:literal) => {
459        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
460            let conn = match self.0.connect().await {
461                Ok(c) => c,
462                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
463            };
464
465            let res = execute!(&conn, $query, &[&x, &(id as i64)]);
466
467            if let Err(e) = res {
468                return Err(Error::DatabaseError(e.to_string()));
469            }
470
471            Ok(())
472        }
473    };
474
475    ($name:ident($x:ty) -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
476        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
477            let conn = match self.0.connect().await {
478                Ok(c) => c,
479                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
480            };
481
482            let res = execute!(&conn, $query, &[&x, &(id as i64)]);
483
484            if let Err(e) = res {
485                return Err(Error::DatabaseError(e.to_string()));
486            }
487
488            self.0.1.remove(format!($cache_key_tmpl, id)).await;
489
490            Ok(())
491        }
492    };
493
494    ($name:ident($x:ty) -> $query:literal --serde) => {
495        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
496            let conn = match self.0.connect().await {
497                Ok(c) => c,
498                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
499            };
500
501            let res = execute!(
502                &conn,
503                $query,
504                &[&serde_json::to_string(&x).unwrap(), &(id as i64)]
505            );
506
507            if let Err(e) = res {
508                return Err(Error::DatabaseError(e.to_string()));
509            }
510
511            Ok(())
512        }
513    };
514
515    ($name:ident($x:ty) -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
516        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
517            let conn = match self.0.connect().await {
518                Ok(c) => c,
519                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
520            };
521
522            let res = execute!(
523                &conn,
524                $query,
525                params![&serde_json::to_string(&x).unwrap(), &(id as i64)]
526            );
527
528            if let Err(e) = res {
529                return Err(Error::DatabaseError(e.to_string()));
530            }
531
532            self.0.1.remove(format!($cache_key_tmpl, id)).await;
533
534            Ok(())
535        }
536    };
537
538    ($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --incr) => {
539        pub async fn $name(&self, id: usize) -> Result<()> {
540            let conn = match self.0.connect().await {
541                Ok(c) => c,
542                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
543            };
544
545            let res = execute!(&conn, $query, &[&(id as i64)]);
546
547            if let Err(e) = res {
548                return Err(Error::DatabaseError(e.to_string()));
549            }
550
551            self.0.1.remove(format!($cache_key_tmpl, id)).await;
552
553            Ok(())
554        }
555    };
556
557    ($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --decr) => {
558        pub async fn $name(&self, id: usize) -> Result<()> {
559            let conn = match self.0.connect().await {
560                Ok(c) => c,
561                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
562            };
563
564            let res = execute!(&conn, $query, &[&(id as i64)]);
565
566            if let Err(e) = res {
567                return Err(Error::DatabaseError(e.to_string()));
568            }
569
570            self.0.1.remove(format!($cache_key_tmpl, id)).await;
571
572            Ok(())
573        }
574    };
575
576    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --decr=$field:ident) => {
577        pub async fn $name(&self, id: usize) -> Result<()> {
578            let y = self.$select_fn(id).await?;
579
580            if (y.$field as isize) - 1 < 0 {
581                return Ok(());
582            }
583
584            let conn = match self.0.connect().await {
585                Ok(c) => c,
586                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
587            };
588
589            let res = execute!(&conn, $query, &[&(id as i64)]);
590
591            if let Err(e) = res {
592                return Err(Error::DatabaseError(e.to_string()));
593            }
594
595            self.0.1.remove(format!($cache_key_tmpl, id)).await;
596
597            Ok(())
598        }
599    };
600
601    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
602        pub async fn $name(&self, id: usize, user: &User) -> Result<()> {
603            let y = self.$select_fn(id).await?;
604
605            if user.id != y.owner {
606                if !user.permissions.check($permission) {
607                    return Err(Error::NotAllowed);
608                } else {
609                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
610                        user.id,
611                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
612                    ))
613                    .await?
614                }
615            }
616
617            let conn = match self.0.connect().await {
618                Ok(c) => c,
619                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
620            };
621
622            let res = execute!(&conn, $query, &[&(id as i64)]);
623
624            if let Err(e) = res {
625                return Err(Error::DatabaseError(e.to_string()));
626            }
627
628            self.$cache_key_tmpl(&y).await;
629
630            Ok(())
631        }
632    };
633
634    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
635        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
636            let y = self.$select_fn(id).await?;
637
638            if user.id != y.owner {
639                if !user.permissions.check($permission) {
640                    return Err(Error::NotAllowed);
641                } else {
642                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
643                        user.id,
644                        format!("invoked `{}` with x value `{x}`", stringify!($name)),
645                    ))
646                    .await?
647                }
648            }
649
650            let conn = match self.0.connect().await {
651                Ok(c) => c,
652                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
653            };
654
655            let res = execute!(&conn, $query, params![&x, &(id as i64)]);
656
657            if let Err(e) = res {
658                return Err(Error::DatabaseError(e.to_string()));
659            }
660
661            self.$cache_key_tmpl(&y).await;
662
663            Ok(())
664        }
665    };
666
667    ($name:ident($x:ty)@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
668        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
669            let y = self.$select_fn(id).await?;
670
671            let conn = match self.0.connect().await {
672                Ok(c) => c,
673                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
674            };
675
676            let res = execute!(&conn, $query, params![&x, &(id as i64)]);
677
678            if let Err(e) = res {
679                return Err(Error::DatabaseError(e.to_string()));
680            }
681
682            self.$cache_key_tmpl(&y).await;
683
684            Ok(())
685        }
686    };
687
688    ($name:ident($x:ty)@$select_fn:ident -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:ident) => {
689        pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
690            let y = self.$select_fn(id).await?;
691
692            let conn = match self.0.connect().await {
693                Ok(c) => c,
694                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
695            };
696
697            let res = execute!(
698                &conn,
699                $query,
700                params![&serde_json::to_string(&x).unwrap(), &(id as i64)]
701            );
702
703            if let Err(e) = res {
704                return Err(Error::DatabaseError(e.to_string()));
705            }
706
707            self.$cache_key_tmpl(&y).await;
708
709            Ok(())
710        }
711    };
712
713    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:ident) => {
714        pub async fn $name(&self, id: usize, user: &User, x: $x) -> Result<()> {
715            let y = self.$select_fn(id).await?;
716
717            if user.id != y.owner {
718                if !user.permissions.check($permission) {
719                    return Err(Error::NotAllowed);
720                } else {
721                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
722                        user.id,
723                        format!("invoked `{}` with x value `{x:?}`", stringify!($name)),
724                    ))
725                    .await?
726                }
727            }
728
729            let conn = match self.0.connect().await {
730                Ok(c) => c,
731                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
732            };
733
734            let res = execute!(
735                &conn,
736                $query,
737                params![&serde_json::to_string(&x).unwrap(), &(id as i64)]
738            );
739
740            if let Err(e) = res {
741                return Err(Error::DatabaseError(e.to_string()));
742            }
743
744            self.$cache_key_tmpl(&y).await;
745
746            Ok(())
747        }
748    };
749
750    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --incr) => {
751        pub async fn $name(&self, id: usize) -> Result<()> {
752            let y = self.$select_fn(id).await?;
753
754            let conn = match self.0.connect().await {
755                Ok(c) => c,
756                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
757            };
758
759            let res = execute!(&conn, $query, &[&(id as i64)]);
760
761            if let Err(e) = res {
762                return Err(Error::DatabaseError(e.to_string()));
763            }
764
765            self.$cache_key_tmpl(&y).await;
766
767            Ok(())
768        }
769    };
770
771    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --decr=$field:ident) => {
772        pub async fn $name(&self, id: usize) -> Result<()> {
773            let y = self.$select_fn(id).await?;
774
775            if (y.$field as isize) - 1 < 0 {
776                return Ok(());
777            }
778
779            let conn = match self.0.connect().await {
780                Ok(c) => c,
781                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
782            };
783
784            let res = execute!(&conn, $query, &[&(id as i64)]);
785
786            if let Err(e) = res {
787                return Err(Error::DatabaseError(e.to_string()));
788            }
789
790            self.$cache_key_tmpl(&y).await;
791
792            Ok(())
793        }
794    };
795}