From d3d2510f7ab9a767b0bd949cb85dce129b9b9c37 Mon Sep 17 00:00:00 2001 From: SadlyNotSappho Date: Mon, 15 Apr 2024 12:00:31 -0700 Subject: [PATCH] add GET /api/posts/, returns json post data --- .gitignore | 1 + src/main.rs | 10 ++++-- src/routes/posts.rs | 85 +++++++++++++++++++++++++++++++++++++-------- src/tables/posts.rs | 53 +++++++++++++++++++--------- 4 files changed, 116 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index e4b1af8..56aab94 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ db/password.txt .env images/*.png tmpimages/*.png +rustc-*.txt diff --git a/src/main.rs b/src/main.rs index ed8cb38..f78b484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,7 @@ async fn migrate(rocket: Rocket) -> Rocket { let _ = conn .execute(sqlx::query( "ALTER TABLE posts - ADD COLUMN IF NOT EXISTS author STRING NOT NULL", + ADD COLUMN IF NOT EXISTS author TEXT NOT NULL", )) .await; let _ = conn @@ -129,6 +129,12 @@ async fn migrate(rocket: Rocket) -> Rocket { ADD COLUMN IF NOT EXISTS timestamp TIMESTAMP DEFAULT localtimestamp NOT NULL", )) .await; + let _ = conn + .execute(sqlx::query( + "ALTER TABLE posts + ADD COLUMN IF NOT EXISTS last_edited_timestamp TIMESTAMP", + )) + .await; rocket } @@ -176,7 +182,7 @@ async fn main() { ) .mount( "/api", - routes![routes::users::api_perms, routes::posts::get_post], + routes![routes::users::api_perms, routes::posts::get_post, routes::posts::update_post], ) .mount("/css", FileServer::from("/srv/web/css")) .register("/", catchers![default_catcher]) diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 2ed7141..0c7f5f6 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,7 +1,7 @@ /* * POST /posts: uses json request body to get the post info and, y'know, create it -* TODO GET /posts/: figures out what type of id is, gets post via that, returns it -* TODO UPDATE /posts/: figures out what type of id is, uses json request body to update +* GET /posts/: figures out what type of id is, gets post via that, returns it +* TODO PATCH /posts/: figures out what type of id is, uses json request body to update * specific data about the post * TODO DELETE /posts/: you can figure out what this one does * @@ -15,10 +15,10 @@ use rocket::http::Status; use rocket::response::status; use rocket::serde::json::Json; use rocket::serde::Deserialize; -use rocket::{get, post}; +use rocket::serde::Serialize; +use rocket::{get, patch, post}; use rocket_db_pools::Connection; use uuid::Uuid; -use rocket::serde::Serialize; #[derive(Deserialize)] #[serde(crate = "rocket::serde")] @@ -26,7 +26,7 @@ pub struct PostCreateInfo { pub title: String, pub text_id: String, pub body: String, - pub auto_pubilsh: bool, + pub auto_publish: bool, } #[post("/posts", data = "")] @@ -40,25 +40,28 @@ pub async fn create( match user.make_posts || user.admin { true => { // if post with same text id exists, fail - match Post::get_by_text_id(&mut db, &info.text_id).await { + match Post::get_by_text_id(&mut db, &info.text_id, false).await { Some(_) => status::Custom( Status::Forbidden, "A post already exists with this text id.".to_string(), ), None => { // create post + let uuid = Uuid::new_v4().to_string(); match Post::create( &mut db, &info.title, &info.body, - info.auto_pubilsh, - Uuid::new_v4().to_string(), + info.auto_publish, + &uuid, &info.text_id, user.username, ) .await { - Some(_) => status::Custom(Status::Ok, "Created.".to_string()), + Some(_) => { + status::Custom(Status::Ok, format!("Created with uuid {uuid}")) + } None => status::Custom( Status::InternalServerError, "Couldn't create post.".to_string(), @@ -90,13 +93,65 @@ pub struct ApiPost { body: String, timestamp: String, uuid: String, - published: bool + // published: bool, } #[get("/posts/")] -pub async fn get_post(mut db: Connection, id: String) -> status::Custom, String>> { - // get post by uuid - // if none, get post by text id - // if none, return none - status::Custom(Status::NotImplemented, Err("Not implemented yet.".to_string())) +pub async fn get_post( + mut db: Connection, + id: String, +) -> status::Custom, String>> { + // get uuid + match match Post::get_by_uuid(&mut db, &id, false).await { + Some(p) => Some(p), + None => match Post::get_by_text_id(&mut db, &id, false).await { + Some(p) => Some(p), + None => None, + }, + } { + Some(p) => { + if p.published { + status::Custom( + Status::Ok, + Ok(Json(ApiPost { + title: p.title, + author: p.author, + text_id: p.text_id, + body: p.body, + uuid: p.uuid, + timestamp: p.timestamp.to_string(), + // published: p.published, + })), + ) + } else { + status::Custom(Status::NotFound, Err("Post doesn't exist".to_string())) + } + } + None => status::Custom(Status::NotFound, Err("Post doesn't exist".to_string())), + } +} +#[derive(Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct PostUpdateNew { + pub title: String, + pub text_id: String, + pub body: String, + pub published: bool, +} + +#[derive(Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct PostUpdateInfo { + pub uuid: String, + pub new: PostUpdateNew, +} + +#[patch /* because of course it's patch and not update */ ("/posts/", data = "")] +pub async fn update_post( + mut db: Connection, + id: String, + info: Json, +) -> status::Custom<&'static str> { + // TODO + status::Custom(Status::NotImplemented, "Not implemented yet.") } diff --git a/src/tables/posts.rs b/src/tables/posts.rs index 69d0a79..aa833f3 100644 --- a/src/tables/posts.rs +++ b/src/tables/posts.rs @@ -14,6 +14,7 @@ pub struct Post { pub body: String, pub published: bool, pub timestamp: NaiveDateTime, + pub author: String, } impl Post { pub async fn create( @@ -21,9 +22,9 @@ impl Post { title: &String, /*ex: Why Trans People Deserve All Your Money*/ body: &String, /*ex: # Because we're cooler than you \n\n![trans flag image](https://sadlynotsappho.dev/pfp.png)*/ published: bool, - uuid: String, + uuid: &String, text_id: &String, /*ex: why-trans-people-deserve-all-your-money */ - author: String + author: String, ) -> Option<()> { match db .execute( @@ -49,7 +50,7 @@ impl Post { } } } - pub async fn get(mut db: Connection, id: i32) -> Post { + pub async fn get(db: &mut Connection, id: i32) -> Post { let res = db .fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id)) .await @@ -62,24 +63,41 @@ impl Post { body: res.get::("body"), published: res.get::("published"), timestamp: res.get::("timestamp"), + author: res.get::("author"), } } - pub async fn get_by_uuid(mut db: Connection, uuid: String) -> Post { - let res = db + pub async fn get_by_uuid( + db: &mut Connection, + uuid: &String, + log_errors: bool, + ) -> Option { + match db .fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid)) .await - .unwrap(); - Post { - id: res.get::("id"), - uuid: res.get::("uuid"), - text_id: res.get::("text_id"), - title: res.get::("title"), - body: res.get::("body"), - published: res.get::("published"), - timestamp: res.get::("timestamp"), + { + Ok(res) => Some(Post { + id: res.get::("id"), + uuid: res.get::("uuid"), + text_id: res.get::("text_id"), + title: res.get::("title"), + body: res.get::("body"), + published: res.get::("published"), + timestamp: res.get::("timestamp"), + author: res.get::("author"), + }), + Err(why) => { + if log_errors { + eprintln!("{why:?}") + } + None + } } } - pub async fn get_by_text_id(db: &mut Connection, text_id: &String) -> Option { + pub async fn get_by_text_id( + db: &mut Connection, + text_id: &String, + log_errors: bool, + ) -> Option { match db .fetch_one(sqlx::query("SELECT * FROM posts WHERE text_id = $1;").bind(text_id)) .await @@ -92,9 +110,12 @@ impl Post { body: res.get::("body"), published: res.get::("published"), timestamp: res.get::("timestamp"), + author: res.get::("author"), }), Err(why) => { - eprintln!("{why:?}"); + if log_errors { + eprintln!("{why:?}") + } None } }