add: mail ui

This commit is contained in:
trisua 2025-08-02 16:04:50 -04:00
parent 2e60cbc464
commit b2a73d286b
24 changed files with 993 additions and 259 deletions

View file

@ -388,6 +388,7 @@ fn default_banned_usernames() -> Vec<String> {
"app".to_string(),
"services".to_string(),
"domains".to_string(),
"mail".to_string(),
]
}

View file

@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS letters (
receivers TEXT NOT NULL,
subject TEXT NOT NULL,
content TEXT NOT NULL,
read_by TEXT NOT NULL
read_by TEXT NOT NULL,
replying_to BIGINT NOT NULL
)

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::model::{auth::User, mail::Letter, permissions::SecondaryPermission, Error, Result};
use crate::{auto_method, DataManager};
use oiseau::{cache::Cache, execute, get, params, query_rows, PostgresRow};
@ -13,7 +15,7 @@ impl DataManager {
subject: get!(x->4(String)),
content: get!(x->5(String)),
read_by: serde_json::from_str(&get!(x->6(String))).unwrap(),
replying_to: get!(x->7(i32)) as usize,
replying_to: get!(x->7(i64)) as usize,
}
}
@ -23,7 +25,14 @@ impl DataManager {
///
/// # Arguments
/// * `id` - the ID of the user to fetch letters for
pub async fn get_letters_by_user(&self, id: usize) -> Result<Vec<Letter>> {
/// * `batch` - the limit of items in each page
/// * `page` - the page number
pub async fn get_letters_by_user(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Letter>> {
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
@ -31,8 +40,8 @@ impl DataManager {
let res = query_rows!(
&conn,
"SELECT * FROM letters WHERE owner = $1 ORDER BY created DESC",
&[&(id as i64)],
"SELECT * FROM letters WHERE owner = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|x| { Self::get_letter_from_row(x) }
);
@ -47,7 +56,14 @@ impl DataManager {
///
/// # Arguments
/// * `id` - the ID of the user to fetch letters for
pub async fn get_received_letters_by_user(&self, id: usize) -> Result<Vec<Letter>> {
/// * `batch` - the limit of items in each page
/// * `page` - the page number
pub async fn get_received_letters_by_user(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Letter>> {
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
@ -55,8 +71,12 @@ impl DataManager {
let res = query_rows!(
&conn,
"SELECT * FROM letters WHERE receivers LIKE $1 ORDER BY created DESC",
&[&format!("%\"{id}\"%")],
"SELECT * FROM letters WHERE receivers LIKE $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
&[
&format!("%{id}%"),
&(batch as i64),
&((page * batch) as i64)
],
|x| { Self::get_letter_from_row(x) }
);
@ -71,7 +91,14 @@ impl DataManager {
///
/// # Arguments
/// * `id` - the ID of the letter to fetch letters for
pub async fn get_letters_by_replying_to(&self, id: usize) -> Result<Vec<Letter>> {
/// * `batch` - the limit of items in each page
/// * `page` - the page number
pub async fn get_letters_by_replying_to(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Letter>> {
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
@ -79,8 +106,8 @@ impl DataManager {
let res = query_rows!(
&conn,
"SELECT * FROM letters WHERE replying_to = $1 ORDER BY created DESC",
&[&(id as i64)],
"SELECT * FROM letters WHERE replying_to = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|x| { Self::get_letter_from_row(x) }
);
@ -91,6 +118,24 @@ impl DataManager {
Ok(res.unwrap())
}
/// Fill a list of letters with their owner.
pub async fn fill_letters(&self, letters: Vec<Letter>) -> Result<Vec<(User, Letter)>> {
let mut seen_users: HashMap<usize, User> = HashMap::new();
let mut out = Vec::new();
for letter in letters {
out.push(if let Some(ua) = seen_users.get(&letter.owner) {
(ua.to_owned(), letter)
} else {
let user = self.get_user_by_id(letter.owner).await?;
seen_users.insert(letter.owner, user.clone());
(user, letter)
})
}
Ok(out)
}
/// Create a new letter in the database.
///
/// # Arguments
@ -109,6 +154,12 @@ impl DataManager {
return Err(Error::DataTooLong("content".to_string()));
}
if data.receivers.len() < 1 {
return Err(Error::DataTooShort("receivers".to_string()));
} else if data.receivers.len() > 10 {
return Err(Error::DataTooLong("receivers".to_string()));
}
// ...
let conn = match self.0.connect().await {
Ok(c) => c,

View file

@ -273,6 +273,9 @@ pub struct UserSettings {
/// If other users that you aren't following can add you to chats.
#[serde(default)]
pub private_chats: bool,
/// If other users that you aren't following can send you letters.
#[serde(default)]
pub private_mails: bool,
/// The user's status. Shows over connection info.
#[serde(default)]
pub status: String,