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}