use axum::{ response::IntoResponse, extract::{Json, Path}, Extension, }; use axum_extra::extract::CookieJar; use tetratto_shared::snow::Snowflake; use crate::{ get_user_from_token, routes::api::v1::{ AddJournalDir, CreateJournal, RemoveJournalDir, UpdateJournalPrivacy, UpdateJournalTitle, }, State, }; use tetratto_core::{ database::NAME_REGEX, model::{ auth::AchievementName, journals::{Journal, JournalPrivacyPermission}, oauth, permissions::FinePermission, ApiReturn, Error, }, }; pub async fn get_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let journal = match data.get_journal_by_id(id).await { Ok(x) => x, Err(e) => return Json(e.into()), }; if journal.privacy == JournalPrivacyPermission::Private && user.id != journal.owner && !user.permissions.contains(FinePermission::MANAGE_JOURNALS) { return Json(Error::NotAllowed.into()); } Json(ApiReturn { ok: true, message: "Success".to_string(), payload: Some(journal), }) } pub async fn get_css_request( Path(id): Path, Extension(data): Extension, ) -> impl IntoResponse { let data = &(data.read().await).0; let note = match data.get_note_by_journal_title(id, "journal.css").await { Ok(x) => x, Err(e) => { return ( [("Content-Type", "text/css"), ("Cache-Control", "no-cache")], format!("/* {e} */"), ); } }; ( [("Content-Type", "text/css"), ("Cache-Control", "no-cache")], note.content, ) } pub async fn list_request(jar: CookieJar, Extension(data): Extension) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data.get_journals_by_user(user.id).await { Ok(x) => Json(ApiReturn { ok: true, message: "Success".to_string(), payload: Some(x), }), Err(e) => Json(e.into()), } } pub async fn create_request( jar: CookieJar, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let mut user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data .create_journal(Journal::new(user.id, props.title)) .await { Ok(x) => { // award achievement if let Err(e) = data .add_achievement(&mut user, AchievementName::CreateJournal.into()) .await { return Json(e.into()); } // ... Json(ApiReturn { ok: true, message: "Journal created".to_string(), payload: Some(x.id.to_string()), }) } Err(e) => Json(e.into()), } } pub async fn update_title_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, Json(mut props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; props.title = props.title.replace(" ", "_").to_lowercase(); // check name let regex = regex::RegexBuilder::new(NAME_REGEX) .multi_line(true) .build() .unwrap(); if regex.captures(&props.title).is_some() { return Json(Error::MiscError("This title contains invalid characters".to_string()).into()); } // make sure this title isn't already in use if data .get_journal_by_owner_title(user.id, &props.title) .await .is_ok() { return Json(Error::TitleInUse.into()); } // ... match data.update_journal_title(id, &user, &props.title).await { Ok(_) => Json(ApiReturn { ok: true, message: "Journal updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn update_privacy_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data.update_journal_privacy(id, &user, props.privacy).await { Ok(_) => Json(ApiReturn { ok: true, message: "Journal updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn delete_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageJournals) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data.delete_journal(id, &user).await { Ok(_) => Json(ApiReturn { ok: true, message: "Journal deleted".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn add_dir_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; if props.name.len() > 32 { return Json(Error::DataTooLong("name".to_string()).into()); } let mut journal = match data.get_journal_by_id(id).await { Ok(x) => x, Err(e) => return Json(e.into()), }; // add dir journal.dirs.push(( Snowflake::new().to_string().parse::().unwrap(), match props.parent.parse() { Ok(p) => p, Err(_) => return Json(Error::Unknown.into()), }, props.name, )); // ... match data.update_journal_dirs(id, &user, journal.dirs).await { Ok(_) => Json(ApiReturn { ok: true, message: "Journal updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn remove_dir_request( jar: CookieJar, Path(id): Path, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let mut journal = match data.get_journal_by_id(id).await { Ok(x) => x, Err(e) => return Json(e.into()), }; // add dir let dir_id: usize = match props.dir.parse() { Ok(x) => x, Err(_) => return Json(Error::Unknown.into()), }; journal .dirs .remove(match journal.dirs.iter().position(|x| x.0 == dir_id) { Some(idx) => idx, None => return Json(Error::GeneralNotFound("directory".to_string()).into()), }); // ... match data.update_journal_dirs(id, &user, journal.dirs).await { Ok(_) => Json(ApiReturn { ok: true, message: "Journal updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } }