add: mail base
This commit is contained in:
parent
a337e0c7c1
commit
29155ddb0c
11 changed files with 211 additions and 3 deletions
|
@ -406,6 +406,7 @@
|
|||
MANAGE_SERVICES: 1 << 3,
|
||||
MANAGE_PRODUCTS: 1 << 4,
|
||||
DEVELOPER_PASS: 1 << 5,
|
||||
MANAGE_LETTERS: 1 << 6,
|
||||
},
|
||||
\"secondary_role\",
|
||||
\"add_permission_to_secondary_role\",
|
||||
|
|
|
@ -100,14 +100,29 @@ impl DataManager {
|
|||
tokens: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
|
||||
permissions: FinePermission::from_bits(get!(x->7(i32)) as u32).unwrap(),
|
||||
is_verified: get!(x->8(i32)) as i8 == 1,
|
||||
notification_count: get!(x->9(i32)) as usize,
|
||||
notification_count: {
|
||||
let x = get!(x->9(i32));
|
||||
if x > (usize::MAX - 1000) as i32 {
|
||||
// we're a little too close to the maximum count, clearly something's gone wrong
|
||||
0
|
||||
} else {
|
||||
x as usize
|
||||
}
|
||||
},
|
||||
follower_count: get!(x->10(i32)) as usize,
|
||||
following_count: get!(x->11(i32)) as usize,
|
||||
last_seen: get!(x->12(i64)) as usize,
|
||||
totp: get!(x->13(String)),
|
||||
recovery_codes: serde_json::from_str(&get!(x->14(String)).to_string()).unwrap(),
|
||||
post_count: get!(x->15(i32)) as usize,
|
||||
request_count: get!(x->16(i32)) as usize,
|
||||
request_count: {
|
||||
let x = get!(x->16(i32));
|
||||
if x > (usize::MAX - 1000) as i32 {
|
||||
0
|
||||
} else {
|
||||
x as usize
|
||||
}
|
||||
},
|
||||
connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
|
||||
stripe_id: get!(x->18(String)),
|
||||
grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
|
||||
|
|
|
@ -44,6 +44,7 @@ impl DataManager {
|
|||
execute!(&conn, common::CREATE_TABLE_SERVICES).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_PRODUCTS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_APP_DATA).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_LETTERS).unwrap();
|
||||
|
||||
for x in common::VERSION_MIGRATIONS.split(";") {
|
||||
execute!(&conn, x).unwrap();
|
||||
|
|
|
@ -32,3 +32,4 @@ pub const CREATE_TABLE_DOMAINS: &str = include_str!("./sql/create_domains.sql");
|
|||
pub const CREATE_TABLE_SERVICES: &str = include_str!("./sql/create_services.sql");
|
||||
pub const CREATE_TABLE_PRODUCTS: &str = include_str!("./sql/create_products.sql");
|
||||
pub const CREATE_TABLE_APP_DATA: &str = include_str!("./sql/create_app_data.sql");
|
||||
pub const CREATE_TABLE_LETTERS: &str = include_str!("./sql/create_letters.sql");
|
||||
|
|
9
crates/core/src/database/drivers/sql/create_letters.sql
Normal file
9
crates/core/src/database/drivers/sql/create_letters.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS letters (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
created BIGINT NOT NULL,
|
||||
owner BIGINT NOT NULL,
|
||||
receivers TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
read_by TEXT NOT NULL
|
||||
)
|
144
crates/core/src/database/letters.rs
Normal file
144
crates/core/src/database/letters.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
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};
|
||||
|
||||
impl DataManager {
|
||||
/// Get a [`Letter`] from an SQL row.
|
||||
pub(crate) fn get_letter_from_row(x: &PostgresRow) -> Letter {
|
||||
Letter {
|
||||
id: get!(x->0(i64)) as usize,
|
||||
created: get!(x->1(i64)) as usize,
|
||||
owner: get!(x->2(i64)) as usize,
|
||||
receivers: serde_json::from_str(&get!(x->3(String))).unwrap(),
|
||||
subject: get!(x->4(String)),
|
||||
content: get!(x->5(String)),
|
||||
read_by: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
auto_method!(get_letter_by_id(usize as i64)@get_letter_from_row -> "SELECT * FROM letters WHERE id = $1" --name="letter" --returns=Letter --cache-key-tmpl="atto.letter:{}");
|
||||
|
||||
/// Get all letters by user.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `id` - the ID of the user to fetch letters for
|
||||
pub async fn get_letters_by_user(&self, id: usize) -> Result<Vec<Letter>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM letters WHERE owner = $1 ORDER BY created DESC",
|
||||
&[&(id as i64)],
|
||||
|x| { Self::get_letter_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("letter".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all letters by user (where user is a receiver).
|
||||
///
|
||||
/// # 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>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM letters WHERE receivers LIKE $1 ORDER BY created DESC",
|
||||
&[&format!("%\"{id}\"%")],
|
||||
|x| { Self::get_letter_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("letter".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new letter in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`Letter`] object to insert
|
||||
pub async fn create_letter(&self, data: Letter) -> Result<Letter> {
|
||||
// check values
|
||||
if data.subject.len() < 2 {
|
||||
return Err(Error::DataTooShort("subject".to_string()));
|
||||
} else if data.subject.len() > 256 {
|
||||
return Err(Error::DataTooLong("subject".to_string()));
|
||||
}
|
||||
|
||||
if data.content.len() < 2 {
|
||||
return Err(Error::DataTooShort("content".to_string()));
|
||||
} else if data.content.len() > 16384 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO letters VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
&(data.owner as i64),
|
||||
&serde_json::to_string(&data.receivers).unwrap(),
|
||||
&data.subject,
|
||||
&data.content,
|
||||
&serde_json::to_string(&data.read_by).unwrap(),
|
||||
]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn delete_letter(&self, id: usize, user: &User) -> Result<()> {
|
||||
let letter = self.get_letter_by_id(id).await?;
|
||||
|
||||
// check user permission
|
||||
if user.id != letter.owner
|
||||
&& !user
|
||||
.secondary_permissions
|
||||
.check(SecondaryPermission::MANAGE_LETTERS)
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(&conn, "DELETE FROM letters WHERE id = $1", &[&(id as i64)]);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
self.0.1.remove(format!("atto.letter:{}", id)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
auto_method!(update_letter_read_by(Vec<usize>) -> "UPDATE letters SET read_by = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.letter:{}");
|
||||
}
|
|
@ -14,6 +14,7 @@ mod invite_codes;
|
|||
mod ipbans;
|
||||
mod ipblocks;
|
||||
mod journals;
|
||||
mod letters;
|
||||
mod memberships;
|
||||
mod message_reactions;
|
||||
mod messages;
|
||||
|
|
35
crates/core/src/model/mail.rs
Normal file
35
crates/core/src/model/mail.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
||||
|
||||
/// A letter is the most basic structure of the mail system. Letters are sent
|
||||
/// and received by users.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Letter {
|
||||
pub id: usize,
|
||||
pub created: usize,
|
||||
pub owner: usize,
|
||||
pub receivers: Vec<usize>,
|
||||
pub subject: String,
|
||||
pub content: String,
|
||||
/// The ID of every use who has read the letter. Can be checked in the UI
|
||||
/// with `user.id in letter.read_by`.
|
||||
///
|
||||
/// This field can be updated by anyone in the letter's `receivers` field.
|
||||
/// Other fields in the letter can only be updated by the letter's `owner`.
|
||||
pub read_by: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Letter {
|
||||
/// Create a new [`Letter`].
|
||||
pub fn new(owner: usize, receivers: Vec<usize>, subject: String, content: String) -> Self {
|
||||
Self {
|
||||
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
|
||||
created: unix_epoch_timestamp(),
|
||||
owner,
|
||||
receivers,
|
||||
subject,
|
||||
content,
|
||||
read_by: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ pub mod communities;
|
|||
pub mod communities_permissions;
|
||||
pub mod journals;
|
||||
pub mod littleweb;
|
||||
pub mod mail;
|
||||
pub mod moderation;
|
||||
pub mod oauth;
|
||||
pub mod permissions;
|
||||
|
|
|
@ -178,6 +178,7 @@ bitflags! {
|
|||
const MANAGE_SERVICES = 1 << 3;
|
||||
const MANAGE_PRODUCTS = 1 << 4;
|
||||
const DEVELOPER_PASS = 1 << 5;
|
||||
const MANAGE_LETTERS = 1 << 6;
|
||||
|
||||
const _ = !0;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ pub fn render_markdown_dirty(input: &str) -> String {
|
|||
options.insert(Options::ENABLE_TABLES);
|
||||
options.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
options.insert(Options::ENABLE_SUBSCRIPT);
|
||||
options.insert(Options::ENABLE_SUPERSCRIPT);
|
||||
|
||||
let parser = Parser::new_ext(input, options);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue