diff --git a/Rb.yaml b/Rb.yaml index 8a3f881..4bf3eac 100644 --- a/Rb.yaml +++ b/Rb.yaml @@ -1,8 +1,9 @@ default: address: "0.0.0.0" - ports: 8000 + port: 8000 debug: + port: 8001 keep_alive: 5 read_timeout: 5 write_timeout: 5 diff --git a/src/db/mod.rs b/src/db/mod.rs index b81b9fb..2ce2174 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -6,3 +6,6 @@ pub mod sections; pub use posts::{NewPost, PatchPost, Post}; pub use sections::{NewSection, PatchSection, Section}; + +pub const MAX_POSTS: u32 = 64; +pub const MAX_SECTIONS: u32 = 64; diff --git a/src/db/posts.rs b/src/db/posts.rs index 71e8e54..e2d11d1 100644 --- a/src/db/posts.rs +++ b/src/db/posts.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::schema::{posts, posts::dsl::*}; +/// A post inside the database. #[derive(Queryable, Serialize)] pub struct Post { @@ -16,6 +17,7 @@ pub struct Post pub content: String, } +/// A new post to be added to the database. #[derive(Deserialize, Insertable)] #[table_name = "posts"] #[serde(rename_all = "camelCase")] @@ -27,6 +29,7 @@ pub struct NewPost pub content: String, } +/// A patch to be applied to a row in the database. #[derive(Deserialize, AsChangeset)] #[table_name = "posts"] pub struct PatchPost @@ -37,15 +40,18 @@ pub struct PatchPost pub content: Option, } +/// Get a list of posts, specified by the offset & a limit. The maximum for `limit_` is determined +/// by `super::MAX_POSTS`. pub fn get(conn: &PgConnection, offset_: u32, limit_: u32) -> RbResult> { Ok(posts .offset(offset_.into()) - .limit(limit_.into()) + .limit(std::cmp::min(limit_, super::MAX_POSTS).into()) .load(conn) .map_err(|_| RbError::DbError("Couldn't query posts."))?) } +/// Try to find a post given its id (primary key). pub fn find(conn: &PgConnection, id_: &Uuid) -> RbOption { match posts.find(id_).first(conn) { @@ -55,6 +61,7 @@ pub fn find(conn: &PgConnection, id_: &Uuid) -> RbOption } } +/// Create a new post & store it in the database. pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult { Ok(insert_into(posts) @@ -65,6 +72,7 @@ pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult // TODO check for conflict? } +/// Update a post in the database with a given ID, returning the updated row. pub fn update(conn: &PgConnection, post_id: &Uuid, patch_post: &PatchPost) -> RbResult { Ok(diesel::update(posts.filter(id.eq(post_id))) @@ -73,6 +81,7 @@ pub fn update(conn: &PgConnection, post_id: &Uuid, patch_post: &PatchPost) -> Rb .map_err(|_| RbError::DbError("Couldn't update post."))?) } +/// Delete a post with a given ID. pub fn delete(conn: &PgConnection, post_id: &Uuid) -> RbResult<()> { diesel::delete(posts.filter(id.eq(post_id))) diff --git a/src/db/sections.rs b/src/db/sections.rs index fc5b7a0..c68b0a4 100644 --- a/src/db/sections.rs +++ b/src/db/sections.rs @@ -5,6 +5,7 @@ use uuid::Uuid; use crate::schema::{sections, sections::dsl::*}; +/// A section inside the database. #[derive(Queryable, Serialize)] pub struct Section { @@ -16,6 +17,8 @@ pub struct Section pub has_titles: bool, } +/// A new section to add. Any `Option` values will get replaced by their default value in the +/// database. #[derive(Serialize, Deserialize, Insertable)] #[table_name = "sections"] #[serde(rename_all = "camelCase")] @@ -28,6 +31,7 @@ pub struct NewSection pub has_titles: Option, } +/// A patch to apply to a section. #[derive(Deserialize, AsChangeset)] #[table_name = "sections"] #[serde(rename_all = "camelCase")] @@ -40,15 +44,18 @@ pub struct PatchSection has_titles: Option, } +/// Get an amount of sections from the database, given an offset & limit. The maximum value for +/// `limit_` is determined by `super::MAX_SECTIONS`. pub fn get(conn: &PgConnection, offset_: u32, limit_: u32) -> RbResult> { Ok(sections .offset(offset_.into()) - .limit(limit_.into()) + .limit(std::cmp::min(limit_, super::MAX_SECTIONS).into()) .load(conn) .map_err(|_| RbError::DbError("Couldn't query sections."))?) } +/// Try to find a section given its shortname. pub fn find_with_shortname(conn: &PgConnection, shortname_: &str) -> RbOption
{ match sections.filter(shortname.eq(shortname_)).first(conn) { @@ -58,6 +65,7 @@ pub fn find_with_shortname(conn: &PgConnection, shortname_: &str) -> RbOption RbResult
{ Ok(insert_into(sections) @@ -68,6 +76,7 @@ pub fn create(conn: &PgConnection, new_section: &NewSection) -> RbResult
RbResult
{ Ok(diesel::update(sections.filter(id.eq(post_id))) @@ -76,6 +85,7 @@ pub fn update(conn: &PgConnection, post_id: &Uuid, patch_post: &PatchSection) -> .map_err(|_| RbError::DbError("Couldn't update section."))?) } +// Update a section given its shortname. pub fn update_with_shortname( conn: &PgConnection, shortname_: &str, @@ -88,6 +98,7 @@ pub fn update_with_shortname( .map_err(|_| RbError::DbError("Couldn't update section with shortname."))?) } +/// Delete a section given its ID. pub fn delete(conn: &PgConnection, post_id: &Uuid) -> RbResult<()> { diesel::delete(sections.filter(id.eq(post_id))) diff --git a/src/main.rs b/src/main.rs index 0a5696d..7ca6841 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,19 +27,22 @@ pub fn auth_header() -> rocket::http::Header<'static> return rocket::http::Header::new("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjVjMjM2OTI0NjY4ZDQzZWFiNGNmNDczYjk1YWZiNzgzIiwidXNlcm5hbWUiOiJKb2huIERvZSIsImFkbWluIjp0cnVlLCJleHAiOjE1MTYyMzkwMjIwfQ.if939L9le8LP-dtXnQs-mHPkb-VieRAvAfSu20755jY"); } +/// Used by Rocket to store database connections. #[database("postgres_rb")] pub struct RbDbConn(diesel::PgConnection); +/// Handles all error status codes. #[catch(default)] fn default_catcher(status: Status, _: &Request) -> Value { json!({"status": status.code, "message": ""}) } -embed_migrations!(); - +/// Rocket fairing that executes the necessary migrations in our database. async fn run_db_migrations(rocket: Rocket) -> Result, Rocket> { + embed_migrations!(); + let conn = RbDbConn::get_one(&rocket) .await .expect("database connection"); @@ -50,12 +53,15 @@ async fn run_db_migrations(rocket: Rocket) -> Result, Rocke .await } +/// Struct to deserialize from the config file. It contains any custom configuration our +/// application might need besides the default Rocket variables. #[derive(Debug, Deserialize, Serialize)] pub struct RbConfig { jwt: JwtConf, } +/// The main entrypoint of our program. It launches the Rocket instance. #[launch] fn rocket() -> _ { @@ -69,8 +75,6 @@ fn rocket() -> _ "Run database migrations", run_db_migrations, )) - // .attach(AdHoc::try_on_ignite("Create admin user", create_admin_user)) - // .attach(AdHoc::config::()) .register("/", catchers![default_catcher]) .mount( "/v1/sections", diff --git a/src/v1/posts.rs b/src/v1/posts.rs index 6c273db..3c65e7f 100644 --- a/src/v1/posts.rs +++ b/src/v1/posts.rs @@ -7,6 +7,7 @@ use rocket::serde::json::Json; use crate::RbDbConn; +/// Get one or more posts. #[get("/?&")] pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult>> { @@ -15,6 +16,7 @@ pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult")] pub async fn find(conn: RbDbConn, id: uuid::Uuid) -> RbOption> { @@ -37,6 +40,7 @@ pub async fn find(conn: RbDbConn, id: uuid::Uuid) -> RbOption> .and_then(|p| Some(Json(p)))) } +/// Patch a post given its ID. #[patch("/", data = "")] pub async fn patch( _admin: Admin, @@ -51,6 +55,7 @@ pub async fn patch( )) } +/// Delete a post given its ID.. #[delete("/")] pub async fn delete(_admin: Admin, conn: RbDbConn, id: uuid::Uuid) -> RbResult<()> { diff --git a/src/v1/sections.rs b/src/v1/sections.rs index 4e9d107..1bd3624 100644 --- a/src/v1/sections.rs +++ b/src/v1/sections.rs @@ -9,6 +9,8 @@ use rocket::serde::json::Json; use crate::RbDbConn; +/// Get multiple sections given an offset & a limit. The limit is bound by +/// `rb_blog::db::MAX_SECTIONS`. #[get("/?&")] pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult>> { @@ -18,6 +20,7 @@ pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult")] pub async fn find(conn: RbDbConn, shortname: String) -> RbOption> { @@ -40,6 +44,7 @@ pub async fn find(conn: RbDbConn, shortname: String) -> RbOption", data = "")] pub async fn patch( _admin: Admin, @@ -56,6 +61,7 @@ pub async fn patch( )) } +/// Delete a section given its ID. #[delete("/")] pub async fn delete(_admin: Admin, conn: RbDbConn, id: uuid::Uuid) -> RbResult<()> { diff --git a/test.py b/test.py index eff2639..3e1a1bb 100644 --- a/test.py +++ b/test.py @@ -12,8 +12,8 @@ data = { "shortname": "short", } -r = requests.post("http://localhost:8000/v1/sections", headers=headers, json=data) +r = requests.post("http://localhost:8000/api/v1/sections", headers=headers, json=data) print(r.content) print(r.status_code) -r = requests.get("http://localhost:8000/v1/sections?offset=0&limit=100") +r = requests.get("http://localhost:8000/api/v1/sections?offset=0&limit=100") print(r.json())