Skip to main content

tetratto_core/database/
common.rs

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