use axum::{ response::IntoResponse, extract::{Json, Path}, Extension, }; use crate::cookie::CookieJar; use tetratto_shared::unix_epoch_timestamp; use crate::{ get_user_from_token, routes::api::v1::{ CreateNote, RenderMarkdown, UpdateNoteContent, UpdateNoteDir, UpdateNoteTags, UpdateNoteTitle, }, State, }; use tetratto_core::{ database::NAME_REGEX, model::{ auth::AchievementName, journals::{JournalPrivacyPermission, Note}, oauth, permissions::FinePermission, uploads::CustomEmoji, 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 note = match data.get_note_by_id(id).await { Ok(x) => x, Err(e) => return Json(e.into()), }; let journal = match data.get_journal_by_id(note.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(note), }) } pub async fn list_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()); } match data.get_notes_by_journal(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 user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data .create_note(Note::new( user.id, props.title, match props.journal.parse() { Ok(x) => x, Err(_) => return Json(Error::Unknown.into()), }, props.content, )) .await { Ok(x) => Json(ApiReturn { ok: true, message: "Note 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::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let note = match data.get_note_by_id(id).await { Ok(n) => n, Err(e) => return Json(e.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_note_by_journal_title(note.journal, &props.title) .await .is_ok() { return Json(Error::TitleInUse.into()); } // ... match data.update_note_title(id, &user, &props.title).await { Ok(_) => { // update note global status if note.is_global { if let Err(e) = data.update_note_is_global(id, 0).await { return Json(e.into()); } } // ... Json(ApiReturn { ok: true, message: "Note updated".to_string(), payload: (), }) } Err(e) => Json(e.into()), } } pub async fn update_content_request( jar: CookieJar, Path(id): Path, 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::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; // award achievement if let Err(e) = data .add_achievement(&mut user, AchievementName::EditNote.into(), true) .await { return Json(e.into()); } // ... match data.update_note_content(id, &user, &props.content).await { Ok(_) => { if let Err(e) = data .update_note_edited(id, unix_epoch_timestamp() as i64) .await { return Json(e.into()); } Json(ApiReturn { ok: true, message: "Note 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::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; match data.delete_note(id, &user).await { Ok(_) => Json(ApiReturn { ok: true, message: "Note deleted".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn delete_by_dir_request( jar: CookieJar, Path((journal, id)): Path<(usize, usize)>, Extension(data): Extension, ) -> 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()), }; match data.delete_notes_by_journal_dir(journal, id, &user).await { Ok(_) => Json(ApiReturn { ok: true, message: "Notes deleted".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn render_markdown_request(Json(req): Json) -> impl IntoResponse { tetratto_shared::markdown::render_markdown(&CustomEmoji::replace(&req.content)) .replace("\\@", "@") .replace("%5C@", "@") } pub async fn update_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 note = match data.get_note_by_id(id).await { Ok(x) => x, Err(e) => return Json(e.into()), }; let journal = match data.get_journal_by_id(note.journal).await { Ok(x) => x, Err(e) => return Json(e.into()), }; // make sure dir exists let dir = match props.dir.parse::() { Ok(d) => d, Err(_) => return Json(Error::Unknown.into()), }; if dir != 0 { if journal.dirs.iter().find(|x| x.0 == dir).is_none() { return Json(Error::GeneralNotFound("directory".to_string()).into()); } } // ... match data.update_note_dir(id, &user, dir as i64).await { Ok(_) => Json(ApiReturn { ok: true, message: "Note updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn update_tags_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()), }; match data.update_note_tags(id, &user, props.tags).await { Ok(_) => Json(ApiReturn { ok: true, message: "Note updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn publish_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::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let note = match data.get_note_by_id(id).await { Ok(n) => n, Err(e) => return Json(e.into()), }; if user.id != note.owner { return Json(Error::NotAllowed.into()); } // check count if data.get_user_global_notes_count(user.id).await.unwrap_or(0) >= if user.permissions.check(FinePermission::SUPPORTER) { 10 } else { 5 } { return Json( Error::MiscError( "You already have the maximum number of global notes you can have".to_string(), ) .into(), ); } // make sure note doesn't already exist globally if data.get_global_note_by_title(¬e.title).await.is_ok() { return Json( Error::MiscError( "Note name is already in use globally. Please change the name and try again" .to_string(), ) .into(), ); } // ... match data.update_note_is_global(id, 1).await { Ok(_) => Json(ApiReturn { ok: true, message: "Note updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } } pub async fn unpublish_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::UserManageNotes) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let note = match data.get_note_by_id(id).await { Ok(n) => n, Err(e) => return Json(e.into()), }; if user.id != note.owner { return Json(Error::NotAllowed.into()); } // ... match data.update_note_is_global(id, 0).await { Ok(_) => Json(ApiReturn { ok: true, message: "Note updated".to_string(), payload: (), }), Err(e) => Json(e.into()), } }