add: letters api

This commit is contained in:
trisua 2025-08-02 00:44:23 -04:00
parent 46e38042ce
commit 2e60cbc464
9 changed files with 247 additions and 31 deletions

View 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()),
}
}

View file

@ -5,6 +5,7 @@ pub mod channels;
pub mod communities;
pub mod domains;
pub mod journals;
pub mod letters;
pub mod notes;
pub mod notifications;
pub mod products;
@ -706,6 +707,13 @@ pub fn routes() -> Router {
post(products::update_description_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 {
@ -1208,3 +1216,11 @@ pub struct QueryAppData {
pub query: AppDataSelectQuery,
pub mode: AppDataSelectMode,
}
#[derive(Deserialize)]
pub struct CreateLetter {
pub receivers: Vec<usize>,
pub subject: String,
pub content: String,
pub replying_to: String,
}