diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp
index 6b2832a..883e35c 100644
--- a/crates/app/src/public/html/components.lisp
+++ b/crates/app/src/public/html/components.lisp
@@ -2640,6 +2640,11 @@
("href" "/mail/compose?receivers={{ owner.username }}{% for receiver in letter.receivers %},id%3A{{ receiver }}{% endfor %}&subject=Re%3A%20{{ letter.subject }}&replying_to={{ letter.id }}")
("title" "Reply all")
(icon (text "reply-all")))
+ (div
+ ("class" "flex gap_2 reactions_box")
+ ("hook" "check_reactions")
+ ("hook-arg:id" "{{ letter.id }}")
+ (text "{{ self::likes(id=letter.id, asset_type=\"Letter\", likes=letter.likes, dislikes=letter.dislikes, disable_dislikes=owner.settings.hide_dislikes, secondary=true) }}"))
(text "{% if user and letter.owner == user.id -%}")
(button
("class" "small lowered red")
diff --git a/crates/core/src/database/drivers/sql/create_letters.sql b/crates/core/src/database/drivers/sql/create_letters.sql
index 4668b64..49ff222 100644
--- a/crates/core/src/database/drivers/sql/create_letters.sql
+++ b/crates/core/src/database/drivers/sql/create_letters.sql
@@ -6,5 +6,7 @@ CREATE TABLE IF NOT EXISTS letters (
subject TEXT NOT NULL,
content TEXT NOT NULL,
read_by TEXT NOT NULL,
- replying_to BIGINT NOT NULL
+ replying_to BIGINT NOT NULL,
+ likes INT NOT NULL,
+ dislikes INT NOT NULL
)
diff --git a/crates/core/src/database/drivers/sql/version_migrations.sql b/crates/core/src/database/drivers/sql/version_migrations.sql
index 4598560..38320ee 100644
--- a/crates/core/src/database/drivers/sql/version_migrations.sql
+++ b/crates/core/src/database/drivers/sql/version_migrations.sql
@@ -69,3 +69,11 @@ ADD COLUMN IF NOT EXISTS uploads TEXT DEFAULT '{}';
-- users last_policy_consent
ALTER TABLE users
ADD COLUMN IF NOT EXISTS last_policy_consent BIGINT DEFAULT 0;
+
+-- letters likes
+ALTER TABLE letters
+ADD COLUMN IF NOT EXISTS likes INT DEFAULT 0;
+
+-- letters dislikes
+ALTER TABLE letters
+ADD COLUMN IF NOT EXISTS dislikes INT DEFAULT 0;
diff --git a/crates/core/src/database/letters.rs b/crates/core/src/database/letters.rs
index 4375ebb..ed298d3 100644
--- a/crates/core/src/database/letters.rs
+++ b/crates/core/src/database/letters.rs
@@ -17,6 +17,8 @@ impl DataManager {
content: get!(x->5(String)),
read_by: serde_json::from_str(&get!(x->6(String))).unwrap(),
replying_to: get!(x->7(i64)) as usize,
+ likes: get!(x->8(i32)) as isize,
+ dislikes: get!(x->9(i32)) as isize,
}
}
@@ -172,7 +174,7 @@ impl DataManager {
let res = execute!(
&conn,
- "INSERT INTO letters VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
+ "INSERT INTO letters VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
params![
&(data.id as i64),
&(data.created as i64),
@@ -181,7 +183,9 @@ impl DataManager {
&data.subject,
&data.content,
&serde_json::to_string(&data.read_by).unwrap(),
- &(data.replying_to as i64)
+ &(data.replying_to as i64),
+ &(data.likes as i32),
+ &(data.dislikes as i32),
]
);
@@ -236,4 +240,9 @@ impl DataManager {
}
auto_method!(update_letter_read_by(Vec) -> "UPDATE letters SET read_by = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.letter:{}");
+
+ auto_method!(incr_letter_likes() -> "UPDATE letters SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --incr);
+ auto_method!(incr_letter_dislikes() -> "UPDATE letters SET dislikes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --incr);
+ auto_method!(decr_letter_likes()@get_letter_by_id -> "UPDATE letters SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --decr=likes);
+ auto_method!(decr_letter_dislikes()@get_letter_by_id -> "UPDATE letters SET dislikes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.letter:{}" --decr=dislikes);
}
diff --git a/crates/core/src/database/reactions.rs b/crates/core/src/database/reactions.rs
index c26c3dc..cd531b1 100644
--- a/crates/core/src/database/reactions.rs
+++ b/crates/core/src/database/reactions.rs
@@ -7,7 +7,6 @@ use crate::model::{
Error, Result,
};
use crate::{auto_method, DataManager};
-
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
impl DataManager {
@@ -302,6 +301,31 @@ impl DataManager {
AssetType::User => {
return Err(Error::NotAllowed);
}
+ AssetType::Letter => {
+ if let Err(e) = {
+ if data.is_like {
+ self.incr_letter_likes(data.asset).await
+ } else {
+ self.incr_letter_dislikes(data.asset).await
+ }
+ } {
+ return Err(e);
+ } else if data.is_like {
+ let letter = self.get_letter_by_id(data.asset).await.unwrap();
+
+ if letter.owner != user.id {
+ self.create_notification(Notification::new(
+ "Your letter has received a like!".to_string(),
+ format!(
+ "[@{}](/api/v1/auth/user/find/{}) has liked your [letter](/mail/letter/{})!",
+ user.username, user.id, data.asset
+ ),
+ letter.owner,
+ ))
+ .await?
+ }
+ }
+ }
};
// return
@@ -358,6 +382,13 @@ impl DataManager {
AssetType::User => {
return Err(Error::NotAllowed);
}
+ AssetType::Letter => {
+ if reaction.is_like {
+ self.decr_letter_likes(reaction.asset).await
+ } else {
+ self.decr_letter_dislikes(reaction.asset).await
+ }
+ }?,
};
// return
diff --git a/crates/core/src/model/mail.rs b/crates/core/src/model/mail.rs
index f0771fa..9d64cf8 100644
--- a/crates/core/src/model/mail.rs
+++ b/crates/core/src/model/mail.rs
@@ -20,6 +20,8 @@ pub struct Letter {
pub read_by: Vec,
/// The ID of the letter this letter is replying to.
pub replying_to: usize,
+ pub likes: isize,
+ pub dislikes: isize,
}
impl Letter {
@@ -40,6 +42,8 @@ impl Letter {
content,
read_by: Vec::new(),
replying_to,
+ likes: 0,
+ dislikes: 0,
}
}
diff --git a/crates/core/src/model/reactions.rs b/crates/core/src/model/reactions.rs
index 827c74e..262956e 100644
--- a/crates/core/src/model/reactions.rs
+++ b/crates/core/src/model/reactions.rs
@@ -12,6 +12,8 @@ pub enum AssetType {
Question,
#[serde(alias = "user")]
User,
+ #[serde(alias = "letter")]
+ Letter,
}
#[derive(Clone, Serialize, Deserialize)]