add: letters api
This commit is contained in:
parent
46e38042ce
commit
2e60cbc464
9 changed files with 247 additions and 31 deletions
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -722,9 +722,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emojis"
|
name = "emojis"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a08afd8e599463c275703532e707c767b8c068a826eea9ca8fceaf3435029df"
|
checksum = "dbf035af17e73b37a9ac6b0efda5f1f4974ee6f6080e33dda268086e84fbcbd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf 0.12.1",
|
"phf 0.12.1",
|
||||||
]
|
]
|
||||||
|
@ -1257,7 +1257,7 @@ dependencies = [
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1349,7 +1349,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
@ -2281,7 +2281,7 @@ dependencies = [
|
||||||
"hmac",
|
"hmac",
|
||||||
"md-5",
|
"md-5",
|
||||||
"memchr",
|
"memchr",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"sha2",
|
"sha2",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
]
|
]
|
||||||
|
@ -2447,9 +2447,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.1"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"rand_core 0.9.3",
|
"rand_core 0.9.3",
|
||||||
|
@ -2607,7 +2607,7 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"ryu",
|
"ryu",
|
||||||
"sha1_smol",
|
"sha1_smol",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
|
@ -2904,9 +2904,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.141"
|
version = "1.0.142"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -3104,6 +3104,16 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
@ -3338,7 +3348,7 @@ dependencies = [
|
||||||
"tetratto-l10n",
|
"tetratto-l10n",
|
||||||
"tetratto-shared",
|
"tetratto-shared",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.9.2",
|
"toml 0.9.4",
|
||||||
"totp-rs",
|
"totp-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3348,7 +3358,7 @@ version = "12.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pathbufd",
|
"pathbufd",
|
||||||
"serde",
|
"serde",
|
||||||
"toml 0.9.2",
|
"toml 0.9.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3359,7 +3369,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex_fmt",
|
"hex_fmt",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
@ -3486,9 +3496,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.46.1"
|
version = "1.47.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -3498,9 +3508,9 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2 0.6.0",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3543,8 +3553,8 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
"postgres-types",
|
"postgres-types",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
@ -3599,9 +3609,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.2"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -3668,7 +3678,7 @@ dependencies = [
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
"hmac",
|
"hmac",
|
||||||
"qrcodegen-image",
|
"qrcodegen-image",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
"url",
|
"url",
|
||||||
|
|
|
@ -20,7 +20,7 @@ tower-http = { version = "0.6.6", features = [
|
||||||
"set-header",
|
"set-header",
|
||||||
] }
|
] }
|
||||||
axum = { version = "0.8.4", features = ["macros", "ws"] }
|
axum = { version = "0.8.4", features = ["macros", "ws"] }
|
||||||
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
axum-extra = { version = "0.10.1", features = ["cookie", "multipart"] }
|
axum-extra = { version = "0.10.1", features = ["cookie", "multipart"] }
|
||||||
ammonia = "4.1.1"
|
ammonia = "4.1.1"
|
||||||
tetratto-shared = { path = "../shared" }
|
tetratto-shared = { path = "../shared" }
|
||||||
|
@ -29,7 +29,7 @@ tetratto-l10n = { path = "../l10n" }
|
||||||
image = "0.25.6"
|
image = "0.25.6"
|
||||||
reqwest = { version = "0.12.22", features = ["json", "stream"] }
|
reqwest = { version = "0.12.22", features = ["json", "stream"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.142"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
cf-turnstile = "0.2.0"
|
cf-turnstile = "0.2.0"
|
||||||
contrasted = "0.1.3"
|
contrasted = "0.1.3"
|
||||||
|
@ -42,7 +42,7 @@ async-stripe = { version = "0.41.0", features = [
|
||||||
"runtime-tokio-hyper",
|
"runtime-tokio-hyper",
|
||||||
"connect",
|
"connect",
|
||||||
] }
|
] }
|
||||||
emojis = "0.7.0"
|
emojis = "0.7.1"
|
||||||
webp = "0.3.0"
|
webp = "0.3.0"
|
||||||
nanoneo = "0.2.0"
|
nanoneo = "0.2.0"
|
||||||
cookie = "0.18.1"
|
cookie = "0.18.1"
|
||||||
|
|
178
crates/app/src/routes/api/v1/letters.rs
Normal file
178
crates/app/src/routes/api/v1/letters.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
use axum::{response::IntoResponse, Extension, Json, extract::Path};
|
||||||
|
use tetratto_core::model::{auth::Notification, mail::Letter, oauth, ApiReturn, Error};
|
||||||
|
use crate::{get_user_from_token, State, cookie::CookieJar};
|
||||||
|
use super::CreateLetter;
|
||||||
|
|
||||||
|
pub async fn list_received_request(jar: CookieJar, data: Extension<State>) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let letters = match data.get_received_letters_by_user(user.id).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(letters),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_sent_request(jar: CookieJar, data: Extension<State>) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let letters = match data.get_letters_by_user(user.id).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(letters),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
data: Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let letter = match data.get_letter_by_id(id).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !letter.can_read(&user) {
|
||||||
|
return Json(Error::NotAllowed.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(letter),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
data: Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data.delete_letter(id, &user).await {
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
data: Extension<State>,
|
||||||
|
Json(props): Json<CreateLetter>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data
|
||||||
|
.create_letter(Letter::new(
|
||||||
|
user.id,
|
||||||
|
props.receivers,
|
||||||
|
props.subject,
|
||||||
|
props.content,
|
||||||
|
match props.replying_to.parse() {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => return Json(Error::Unknown.into()),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(l) => {
|
||||||
|
// send notifications
|
||||||
|
for x in &l.receivers {
|
||||||
|
if let Err(e) = data
|
||||||
|
.create_notification(Notification::new(
|
||||||
|
"You've got mail!".to_string(),
|
||||||
|
format!(
|
||||||
|
"[@{}](/api/v1/auth/user/find/{}) has sent you a [letter](/mail/{}).",
|
||||||
|
user.username, user.id, l.id
|
||||||
|
),
|
||||||
|
*x,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Json(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(l),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_read_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
data: Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadLetters) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut letter = match data.get_letter_by_id(id).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !letter.can_read(&user) {
|
||||||
|
return Json(Error::NotAllowed.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if letter.read_by.contains(&user.id) {
|
||||||
|
return Json(Error::MiscError("Already marked as read".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
letter.read_by.push(user.id);
|
||||||
|
match data.update_letter_read_by(id, letter.read_by).await {
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ pub mod channels;
|
||||||
pub mod communities;
|
pub mod communities;
|
||||||
pub mod domains;
|
pub mod domains;
|
||||||
pub mod journals;
|
pub mod journals;
|
||||||
|
pub mod letters;
|
||||||
pub mod notes;
|
pub mod notes;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod products;
|
pub mod products;
|
||||||
|
@ -706,6 +707,13 @@ pub fn routes() -> Router {
|
||||||
post(products::update_description_request),
|
post(products::update_description_request),
|
||||||
)
|
)
|
||||||
.route("/products/{id}/price", post(products::update_price_request))
|
.route("/products/{id}/price", post(products::update_price_request))
|
||||||
|
// letters
|
||||||
|
.route("/letters", post(letters::create_request))
|
||||||
|
.route("/letters/{id}", get(letters::get_request))
|
||||||
|
.route("/letters/{id}", delete(letters::delete_request))
|
||||||
|
.route("/letters/{id}/read", post(letters::add_read_request))
|
||||||
|
.route("/letters/sent", get(letters::list_sent_request))
|
||||||
|
.route("/letters/received", get(letters::list_received_request))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lw_routes() -> Router {
|
pub fn lw_routes() -> Router {
|
||||||
|
@ -1208,3 +1216,11 @@ pub struct QueryAppData {
|
||||||
pub query: AppDataSelectQuery,
|
pub query: AppDataSelectQuery,
|
||||||
pub mode: AppDataSelectMode,
|
pub mode: AppDataSelectMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CreateLetter {
|
||||||
|
pub receivers: Vec<usize>,
|
||||||
|
pub subject: String,
|
||||||
|
pub content: String,
|
||||||
|
pub replying_to: String,
|
||||||
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@ default = ["database", "types", "sdk"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pathbufd = "0.1.4"
|
pathbufd = "0.1.4"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
toml = "0.9.2"
|
toml = "0.9.4"
|
||||||
tetratto-shared = { version = "12.0.6", path = "../shared" }
|
tetratto-shared = { version = "12.0.6", path = "../shared" }
|
||||||
tetratto-l10n = { version = "12.0.0", path = "../l10n" }
|
tetratto-l10n = { version = "12.0.0", path = "../l10n" }
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.142"
|
||||||
totp-rs = { version = "5.7.0", features = ["qr", "gen_secret"], optional = true }
|
totp-rs = { version = "5.7.0", features = ["qr", "gen_secret"], optional = true }
|
||||||
reqwest = { version = "0.12.22", features = ["json", "multipart"], optional = true }
|
reqwest = { version = "0.12.22", features = ["json", "multipart"], optional = true }
|
||||||
bitflags = { version = "2.9.1", optional = true }
|
bitflags = { version = "2.9.1", optional = true }
|
||||||
|
@ -28,11 +28,11 @@ async-recursion = { version = "1.1.1", optional = true }
|
||||||
md-5 = { version = "0.10.6", optional = true }
|
md-5 = { version = "0.10.6", optional = true }
|
||||||
base16ct = { version = "0.2.0", features = ["alloc"], optional = true }
|
base16ct = { version = "0.2.0", features = ["alloc"], optional = true }
|
||||||
base64 = { version = "0.22.1", optional = true }
|
base64 = { version = "0.22.1", optional = true }
|
||||||
emojis = "0.7.0"
|
emojis = "0.7.1"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
oiseau = { version = "0.1.2", default-features = false, features = [
|
oiseau = { version = "0.1.2", default-features = false, features = [
|
||||||
"postgres",
|
"postgres",
|
||||||
"redis",
|
"redis",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
paste = { version = "1.0.15", optional = true }
|
paste = { version = "1.0.15", optional = true }
|
||||||
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
||||||
|
use crate::model::auth::User;
|
||||||
|
|
||||||
/// A letter is the most basic structure of the mail system. Letters are sent
|
/// A letter is the most basic structure of the mail system. Letters are sent
|
||||||
/// and received by users.
|
/// and received by users.
|
||||||
|
@ -41,4 +42,9 @@ impl Letter {
|
||||||
replying_to,
|
replying_to,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the given user can read the letter.
|
||||||
|
pub fn can_read(&self, user: &User) -> bool {
|
||||||
|
(user.id == self.owner) | self.receivers.contains(&user.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,8 @@ pub enum AppScope {
|
||||||
UserReadServices,
|
UserReadServices,
|
||||||
/// Read the user's products.
|
/// Read the user's products.
|
||||||
UserReadProducts,
|
UserReadProducts,
|
||||||
|
/// Read the user's letters.
|
||||||
|
UserReadLetters,
|
||||||
/// Create posts as the user.
|
/// Create posts as the user.
|
||||||
UserCreatePosts,
|
UserCreatePosts,
|
||||||
/// Create messages as the user.
|
/// Create messages as the user.
|
||||||
|
@ -102,6 +104,8 @@ pub enum AppScope {
|
||||||
UserCreateServices,
|
UserCreateServices,
|
||||||
/// Create products on behalf of the user.
|
/// Create products on behalf of the user.
|
||||||
UserCreateProducts,
|
UserCreateProducts,
|
||||||
|
/// Create letters on behalf of the user.
|
||||||
|
UserCreateLetters,
|
||||||
/// Delete posts owned by the user.
|
/// Delete posts owned by the user.
|
||||||
UserDeletePosts,
|
UserDeletePosts,
|
||||||
/// Delete messages owned by the user.
|
/// Delete messages owned by the user.
|
||||||
|
@ -146,6 +150,8 @@ pub enum AppScope {
|
||||||
UserManageProducts,
|
UserManageProducts,
|
||||||
/// Manage the user's channel mutes.
|
/// Manage the user's channel mutes.
|
||||||
UserManageChannelMutes,
|
UserManageChannelMutes,
|
||||||
|
/// Manage the user's letters.
|
||||||
|
UserManageLetters,
|
||||||
/// Edit posts created by the user.
|
/// Edit posts created by the user.
|
||||||
UserEditPosts,
|
UserEditPosts,
|
||||||
/// Edit drafts created by the user.
|
/// Edit drafts created by the user.
|
||||||
|
|
|
@ -11,4 +11,4 @@ homepage.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pathbufd = "0.1.4"
|
pathbufd = "0.1.4"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
toml = "0.9.2"
|
toml = "0.9.4"
|
||||||
|
|
|
@ -12,7 +12,7 @@ ammonia = "4.1.1"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
hex_fmt = "0.3.0"
|
hex_fmt = "0.3.0"
|
||||||
pulldown-cmark = "0.13.0"
|
pulldown-cmark = "0.13.0"
|
||||||
rand = "0.9.1"
|
rand = "0.9.2"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue