tetratto_core2/database/
poll_votes.rs1use oiseau::cache::Cache;
2use crate::model::id::Id;
3use crate::model::posts::{PollOption, PollVote};
4use crate::model::moderation::AuditLogEntry;
5use crate::model::{Error, Result, auth::User, permissions::FinePermission};
6use tritools::time::unix_epoch_timestamp;
7use crate::{auto_method, DataManager};
8
9use oiseau::PostgresRow;
10
11use oiseau::{execute, get, query_row, params};
12
13impl DataManager {
14 pub(crate) fn get_poll_vote_from_row(x: &PostgresRow) -> PollVote {
16 PollVote {
17 id: Id::deserialize(&get!(x->0(String))),
18 owner: Id::deserialize(&get!(x->1(String))),
19 created: get!(x->2(i64)) as u128,
20 poll_id: Id::deserialize(&get!(x->3(String))),
21 vote: (get!(x->4(i32)) as u8).into(),
22 }
23 }
24
25 auto_method!(get_poll_vote_by_id()@get_poll_vote_from_row -> "SELECT * FROM poll_votes WHERE id = $1" --name="poll vote" --returns=PollVote --cache-key-tmpl="atto.poll_vote:{}");
26
27 pub async fn get_poll_vote_by_owner_poll(&self, id: &Id, poll_id: &Id) -> Result<PollVote> {
28 if let Some(cached) = self
29 .0
30 .1
31 .get(format!("atto.poll_vote:{}:{}", id, poll_id))
32 .await
33 {
34 return Ok(serde_json::from_str(&cached).unwrap());
35 }
36
37 let conn = match self.0.connect().await {
38 Ok(c) => c,
39 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
40 };
41
42 let res = query_row!(
43 &conn,
44 "SELECT * FROM poll_votes WHERE owner = $1 AND poll_id = $2",
45 &[&id.printable(), &poll_id.printable()],
46 |x| { Ok(Self::get_poll_vote_from_row(x)) }
47 );
48
49 if res.is_err() {
50 return Err(Error::GeneralNotFound("poll vote".to_string()));
51 }
52
53 let x = res.unwrap();
54 self.0
55 .1
56 .set(
57 format!("atto.poll_vote:{}:{}", id, poll_id),
58 serde_json::to_string(&x).unwrap(),
59 )
60 .await;
61
62 Ok(x)
63 }
64
65 pub async fn create_poll_vote(&self, data: PollVote) -> Result<Id> {
70 let poll = self.get_poll_by_id(&data.poll_id).await?;
72
73 let now = unix_epoch_timestamp();
74 let diff = now - poll.created;
75
76 if diff > poll.expires {
77 return Err(Error::MiscError("Poll is closed".to_string()));
78 };
79
80 let conn = match self.0.connect().await {
82 Ok(c) => c,
83 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
84 };
85
86 let vote_u8: u8 = data.vote.into();
87 let res = execute!(
88 &conn,
89 "INSERT INTO poll_votes VALUES ($1, $2, $3, $4, $5)",
90 params![
91 &data.id.printable(),
92 &data.owner.printable(),
93 &(data.created as i64),
94 &data.poll_id.printable(),
95 &(vote_u8 as i32),
96 ]
97 );
98
99 if let Err(e) = res {
100 return Err(Error::DatabaseError(e.to_string()));
101 }
102
103 match data.vote {
105 PollOption::A => {
106 self.incr_votes_a_count(&poll.id).await?;
107 }
108 PollOption::B => {
109 self.incr_votes_b_count(&poll.id).await?;
110 }
111 PollOption::C => {
112 self.incr_votes_c_count(&poll.id).await?;
113 }
114 PollOption::D => {
115 self.incr_votes_d_count(&poll.id).await?;
116 }
117 };
118
119 Ok(data.id)
121 }
122
123 pub async fn delete_poll_vote(&self, id: &Id, user: User) -> Result<()> {
124 let y = self.get_poll_vote_by_id(id).await?;
125
126 if user.id != y.owner {
127 if !user.permissions.check(FinePermission::ManagePosts) {
128 return Err(Error::NotAllowed);
129 } else {
130 self.create_audit_log_entry(AuditLogEntry::new(
131 user.id,
132 format!("invoked `delete_poll_vote` with x value `{id}`"),
133 ))
134 .await?
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 = execute!(
143 &conn,
144 "DELETE FROM poll_votes WHERE id = $1",
145 &[&id.printable()]
146 );
147
148 if let Err(e) = res {
149 return Err(Error::DatabaseError(e.to_string()));
150 }
151
152 self.0.1.remove(format!("atto.poll_vote:{}", id)).await;
153
154 Ok(())
155 }
156}