generated from t/malachite
add: api routes
This commit is contained in:
parent
d7ee379a9a
commit
ce9ce4f635
16 changed files with 1119 additions and 109 deletions
137
src/routes/api/chats.rs
Normal file
137
src/routes/api/chats.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use crate::{
|
||||
State, get_user_from_token,
|
||||
model::{Chat, ChatStyle, GroupChatInfo},
|
||||
};
|
||||
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use serde::Deserialize;
|
||||
use tetratto_core::model::{ApiReturn, Error};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateChat {
|
||||
pub style: ChatStyle,
|
||||
pub members: Vec<usize>,
|
||||
}
|
||||
|
||||
pub async fn create_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Json(req): Json<CreateChat>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
if req.members.len() > 2 && req.style == ChatStyle::Direct {
|
||||
return Json(Error::DataTooLong("members".to_string()).into());
|
||||
}
|
||||
|
||||
match data
|
||||
.create_chat(Chat::new(req.style, {
|
||||
let mut x = req.members;
|
||||
x.push(user.id);
|
||||
x
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(x) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: x.id.to_string(),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn leave_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
let mut chat = match data.get_chat_by_id(id).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
if !chat.members.contains(&user.id) {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
chat.members
|
||||
.remove(chat.members.iter().position(|x| *x == user.id).unwrap());
|
||||
|
||||
if chat.members.len() == 0 {
|
||||
// we were the last member
|
||||
match data.delete_chat(id).await {
|
||||
Ok(_) => {
|
||||
return Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
});
|
||||
}
|
||||
Err(e) => return Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
match data.update_chat_members(chat.id, chat.members).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateChatInfo {
|
||||
pub info: GroupChatInfo,
|
||||
}
|
||||
|
||||
pub async fn update_info_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
Json(req): Json<UpdateChatInfo>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
let chat = match data.get_chat_by_id(id).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
if !chat.members.contains(&user.id) {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
match chat.style {
|
||||
ChatStyle::Group(_) => {
|
||||
match data
|
||||
.update_chat_style(chat.id, ChatStyle::Group(req.info))
|
||||
.await
|
||||
{
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
_ => return Json(Error::DoesNotSupportField("info".to_string()).into()),
|
||||
}
|
||||
}
|
147
src/routes/api/messages.rs
Normal file
147
src/routes/api/messages.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use crate::{State, get_user_from_token, model::Message};
|
||||
use axum::{Extension, Json, body::Bytes, extract::Path, response::IntoResponse};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use axum_image::{encode::save_webp_buffer, extract::JsonMultipart};
|
||||
use buckets_core::model::{MediaType, MediaUpload};
|
||||
use serde::Deserialize;
|
||||
use tetratto_core::model::{ApiReturn, Error};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateMessage {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
const MAXIMUM_UPLOAD_SIZE: usize = 4_194_304; // 4 MiB
|
||||
|
||||
pub async fn create_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
JsonMultipart(byte_parts, req): JsonMultipart<CreateMessage>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
// check fields
|
||||
if req.content.trim().len() < 2 {
|
||||
return Json(Error::DataTooShort("content".to_string()).into());
|
||||
} else if req.content.len() > 2048 {
|
||||
return Json(Error::DataTooLong("content".to_string()).into());
|
||||
}
|
||||
|
||||
// check chat permissions
|
||||
let chat = match data.get_chat_by_id(id).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
if !chat.members.contains(&user.id) {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
// create uploads
|
||||
let mut uploads: Vec<(MediaUpload, Bytes)> = Vec::new();
|
||||
|
||||
for part in &byte_parts {
|
||||
if part.len() < MAXIMUM_UPLOAD_SIZE {
|
||||
return Json(Error::FileTooLarge.into());
|
||||
}
|
||||
}
|
||||
|
||||
for part in byte_parts {
|
||||
uploads.push((
|
||||
MediaUpload::new(MediaType::Webp, user.id, "message_media".to_string()),
|
||||
part,
|
||||
));
|
||||
}
|
||||
|
||||
// create message
|
||||
match data
|
||||
.create_message(Message::new(
|
||||
user.id,
|
||||
chat.id,
|
||||
req.content,
|
||||
uploads.iter().map(|x| x.0.id).collect(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(x) => {
|
||||
// store uploads
|
||||
for (upload, part) in uploads {
|
||||
let upload = match data.1.create_upload(upload).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if save_webp_buffer(
|
||||
&upload.path(&data.1.0.0.directory).to_string(),
|
||||
part.to_vec(),
|
||||
None,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: x.id.to_string(),
|
||||
})
|
||||
}
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
match data.delete_message(id, &user).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateMessageContent {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
pub async fn update_content_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
Json(req): Json<UpdateMessageContent>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data.2) {
|
||||
Some(x) => x,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
match data.update_message_content(id, &req.content, &user).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
14
src/routes/api/mod.rs
Normal file
14
src/routes/api/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
pub mod chats;
|
||||
pub mod messages;
|
||||
|
||||
use axum::routing::{Router, delete, post, put};
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.route("/chats", post(chats::create_request))
|
||||
.route("/chats/{id}/leave", post(chats::leave_request))
|
||||
.route("/chats/{id}/info", post(chats::update_info_request))
|
||||
.route("/messages", post(messages::create_request))
|
||||
.route("/messages/{id}", delete(messages::delete_request))
|
||||
.route("/messages/{id}", put(messages::update_content_request))
|
||||
}
|
25
src/routes/mod.rs
Normal file
25
src/routes/mod.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::config::Config;
|
||||
use axum::{Router, routing::get_service};
|
||||
use tera::Context;
|
||||
|
||||
pub mod api;
|
||||
pub mod pages;
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.nest_service(
|
||||
"/public",
|
||||
get_service(tower_http::services::ServeDir::new("./public")),
|
||||
)
|
||||
.fallback(pages::misc::not_found_request)
|
||||
.merge(pages::routes())
|
||||
.nest("/api/v1", api::routes())
|
||||
}
|
||||
|
||||
pub fn default_context(config: &Config, build_code: &str) -> Context {
|
||||
let mut ctx = Context::new();
|
||||
ctx.insert("name", &config.name);
|
||||
ctx.insert("theme_color", &config.theme_color);
|
||||
ctx.insert("build_code", &build_code);
|
||||
ctx
|
||||
}
|
25
src/routes/pages/misc.rs
Normal file
25
src/routes/pages/misc.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{State, routes::default_context};
|
||||
use axum::{
|
||||
Extension,
|
||||
response::{Html, IntoResponse},
|
||||
};
|
||||
use tetratto_core::model::Error;
|
||||
|
||||
pub async fn not_found_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
|
||||
let mut ctx = default_context(&data.0.0, &build_code);
|
||||
ctx.insert(
|
||||
"error",
|
||||
&Error::GeneralNotFound("page".to_string()).to_string(),
|
||||
);
|
||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
pub async fn index_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
Html(
|
||||
tera.render("index.lisp", &default_context(&data.0.0, &build_code))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
7
src/routes/pages/mod.rs
Normal file
7
src/routes/pages/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod misc;
|
||||
|
||||
use axum::routing::{Router, get};
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new().route("/", get(misc::index_request))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue