add GET /api/posts/<id>, returns json post data

This commit is contained in:
SadlyNotSappho 2024-04-15 12:00:31 -07:00
parent a9cbf77eaa
commit d3d2510f7a
4 changed files with 116 additions and 33 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ db/password.txt
.env .env
images/*.png images/*.png
tmpimages/*.png tmpimages/*.png
rustc-*.txt

View File

@ -120,7 +120,7 @@ async fn migrate(rocket: Rocket<Build>) -> Rocket<Build> {
let _ = conn let _ = conn
.execute(sqlx::query( .execute(sqlx::query(
"ALTER TABLE posts "ALTER TABLE posts
ADD COLUMN IF NOT EXISTS author STRING NOT NULL", ADD COLUMN IF NOT EXISTS author TEXT NOT NULL",
)) ))
.await; .await;
let _ = conn let _ = conn
@ -129,6 +129,12 @@ async fn migrate(rocket: Rocket<Build>) -> Rocket<Build> {
ADD COLUMN IF NOT EXISTS timestamp TIMESTAMP DEFAULT localtimestamp NOT NULL", ADD COLUMN IF NOT EXISTS timestamp TIMESTAMP DEFAULT localtimestamp NOT NULL",
)) ))
.await; .await;
let _ = conn
.execute(sqlx::query(
"ALTER TABLE posts
ADD COLUMN IF NOT EXISTS last_edited_timestamp TIMESTAMP",
))
.await;
rocket rocket
} }
@ -176,7 +182,7 @@ async fn main() {
) )
.mount( .mount(
"/api", "/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")) .mount("/css", FileServer::from("/srv/web/css"))
.register("/", catchers![default_catcher]) .register("/", catchers![default_catcher])

View File

@ -1,7 +1,7 @@
/* /*
* POST /posts: uses json request body to get the post info and, y'know, create it * POST /posts: uses json request body to get the post info and, y'know, create it
* TODO GET /posts/<id>: figures out what type of id <id> is, gets post via that, returns it * GET /posts/<id>: figures out what type of id <id> is, gets post via that, returns it
* TODO UPDATE /posts/<id>: figures out what type of id <id> is, uses json request body to update * TODO PATCH /posts/<id>: figures out what type of id <id> is, uses json request body to update
* specific data about the post * specific data about the post
* TODO DELETE /posts/<id>: you can figure out what this one does * TODO DELETE /posts/<id>: you can figure out what this one does
* *
@ -15,10 +15,10 @@ use rocket::http::Status;
use rocket::response::status; use rocket::response::status;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::serde::Deserialize; use rocket::serde::Deserialize;
use rocket::{get, post}; use rocket::serde::Serialize;
use rocket::{get, patch, post};
use rocket_db_pools::Connection; use rocket_db_pools::Connection;
use uuid::Uuid; use uuid::Uuid;
use rocket::serde::Serialize;
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
@ -26,7 +26,7 @@ pub struct PostCreateInfo {
pub title: String, pub title: String,
pub text_id: String, pub text_id: String,
pub body: String, pub body: String,
pub auto_pubilsh: bool, pub auto_publish: bool,
} }
#[post("/posts", data = "<info>")] #[post("/posts", data = "<info>")]
@ -40,25 +40,28 @@ pub async fn create(
match user.make_posts || user.admin { match user.make_posts || user.admin {
true => { true => {
// if post with same text id exists, fail // 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( Some(_) => status::Custom(
Status::Forbidden, Status::Forbidden,
"A post already exists with this text id.".to_string(), "A post already exists with this text id.".to_string(),
), ),
None => { None => {
// create post // create post
let uuid = Uuid::new_v4().to_string();
match Post::create( match Post::create(
&mut db, &mut db,
&info.title, &info.title,
&info.body, &info.body,
info.auto_pubilsh, info.auto_publish,
Uuid::new_v4().to_string(), &uuid,
&info.text_id, &info.text_id,
user.username, user.username,
) )
.await .await
{ {
Some(_) => status::Custom(Status::Ok, "Created.".to_string()), Some(_) => {
status::Custom(Status::Ok, format!("Created with uuid {uuid}"))
}
None => status::Custom( None => status::Custom(
Status::InternalServerError, Status::InternalServerError,
"Couldn't create post.".to_string(), "Couldn't create post.".to_string(),
@ -90,13 +93,65 @@ pub struct ApiPost {
body: String, body: String,
timestamp: String, timestamp: String,
uuid: String, uuid: String,
published: bool // published: bool,
} }
#[get("/posts/<id>")] #[get("/posts/<id>")]
pub async fn get_post(mut db: Connection<Db>, id: String) -> status::Custom<Result<Json<ApiPost>, String>> { pub async fn get_post(
// get post by uuid mut db: Connection<Db>,
// if none, get post by text id id: String,
// if none, return none ) -> status::Custom<Result<Json<ApiPost>, String>> {
status::Custom(Status::NotImplemented, Err("Not implemented yet.".to_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/<id>", data = "<info>")]
pub async fn update_post(
mut db: Connection<Db>,
id: String,
info: Json<PostUpdateInfo>,
) -> status::Custom<&'static str> {
// TODO
status::Custom(Status::NotImplemented, "Not implemented yet.")
} }

View File

@ -14,6 +14,7 @@ pub struct Post {
pub body: String, pub body: String,
pub published: bool, pub published: bool,
pub timestamp: NaiveDateTime, pub timestamp: NaiveDateTime,
pub author: String,
} }
impl Post { impl Post {
pub async fn create( pub async fn create(
@ -21,9 +22,9 @@ impl Post {
title: &String, /*ex: Why Trans People Deserve All Your Money*/ 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)*/ body: &String, /*ex: # Because we're cooler than you \n\n![trans flag image](https://sadlynotsappho.dev/pfp.png)*/
published: bool, published: bool,
uuid: String, uuid: &String,
text_id: &String, /*ex: why-trans-people-deserve-all-your-money */ text_id: &String, /*ex: why-trans-people-deserve-all-your-money */
author: String author: String,
) -> Option<()> { ) -> Option<()> {
match db match db
.execute( .execute(
@ -49,7 +50,7 @@ impl Post {
} }
} }
} }
pub async fn get(mut db: Connection<Db>, id: i32) -> Post { pub async fn get(db: &mut Connection<Db>, id: i32) -> Post {
let res = db let res = db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id)) .fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id))
.await .await
@ -62,24 +63,41 @@ impl Post {
body: res.get::<String, _>("body"), body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"), published: res.get::<bool, _>("published"),
timestamp: res.get::<NaiveDateTime, _>("timestamp"), timestamp: res.get::<NaiveDateTime, _>("timestamp"),
author: res.get::<String, _>("author"),
} }
} }
pub async fn get_by_uuid(mut db: Connection<Db>, uuid: String) -> Post { pub async fn get_by_uuid(
let res = db db: &mut Connection<Db>,
uuid: &String,
log_errors: bool,
) -> Option<Post> {
match db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid)) .fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid))
.await .await
.unwrap(); {
Post { Ok(res) => Some(Post {
id: res.get::<i32, _>("id"), id: res.get::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"), uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"), text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"), title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"), body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"), published: res.get::<bool, _>("published"),
timestamp: res.get::<NaiveDateTime, _>("timestamp"), timestamp: res.get::<NaiveDateTime, _>("timestamp"),
author: res.get::<String, _>("author"),
}),
Err(why) => {
if log_errors {
eprintln!("{why:?}")
}
None
}
} }
} }
pub async fn get_by_text_id(db: &mut Connection<Db>, text_id: &String) -> Option<Post> { pub async fn get_by_text_id(
db: &mut Connection<Db>,
text_id: &String,
log_errors: bool,
) -> Option<Post> {
match db match db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE text_id = $1;").bind(text_id)) .fetch_one(sqlx::query("SELECT * FROM posts WHERE text_id = $1;").bind(text_id))
.await .await
@ -92,9 +110,12 @@ impl Post {
body: res.get::<String, _>("body"), body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"), published: res.get::<bool, _>("published"),
timestamp: res.get::<NaiveDateTime, _>("timestamp"), timestamp: res.get::<NaiveDateTime, _>("timestamp"),
author: res.get::<String, _>("author"),
}), }),
Err(why) => { Err(why) => {
eprintln!("{why:?}"); if log_errors {
eprintln!("{why:?}")
}
None None
} }
} }