From 211e31a00834667d7907656cf73233a40dc76ddb Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 13 Sep 2021 17:18:33 +0200 Subject: [PATCH 1/5] Wrote first draft of sections database scheme --- .../2021-09-13-143540_sections/down.sql | 7 +++ migrations/2021-09-13-143540_sections/up.sql | 56 +++++++++++++++++++ src/schema.rs | 28 +++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 migrations/2021-09-13-143540_sections/down.sql create mode 100644 migrations/2021-09-13-143540_sections/up.sql diff --git a/migrations/2021-09-13-143540_sections/down.sql b/migrations/2021-09-13-143540_sections/down.sql new file mode 100644 index 0000000..7af43ff --- /dev/null +++ b/migrations/2021-09-13-143540_sections/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` +drop trigger insert_enforce_post_titles on posts; +drop trigger update_enforce_post_titles on posts; +drop function enforce_post_titles; + +drop table posts cascade; +drop table sections cascade; diff --git a/migrations/2021-09-13-143540_sections/up.sql b/migrations/2021-09-13-143540_sections/up.sql new file mode 100644 index 0000000..0c5ca76 --- /dev/null +++ b/migrations/2021-09-13-143540_sections/up.sql @@ -0,0 +1,56 @@ +-- Your SQL goes here +create table sections ( + id uuid DEFAULT gen_random_uuid() PRIMARY KEY, + + -- Title of the section + title varchar(255) UNIQUE NOT NULL, + -- Optional description of the section + description text, + -- Wether to show the section in the default list on the homepage + is_default boolean NOT NULL DEFAULT false, + -- Wether the posts should contain titles or not + has_titles boolean NOT NULL DEFAULT true +); + +create table posts ( + id uuid DEFAULT gen_random_uuid() PRIMARY KEY, + + section_id uuid NOT NULL REFERENCES sections(id) ON DELETE CASCADE, + -- Title of the post + -- Wether this is NULL or not is enforced using the enforce_post_titles trigger + title varchar(255), + -- Post date, defaults to today + publish_date date NOT NULL DEFAULT now(), + -- Content of the post + content text NOT NULL +); + +create function enforce_post_titles() returns trigger as $enforce_post_titles$ + begin + -- Check for a wrongfully null title + if new.title is null and exists ( + select 1 from sections where id = new.section_id and has_titles + ) then + raise exception 'Expected a post title, but got null.'; + end if; + + if new.title is not null and exists ( + select 1 from sections where id = new.section_id and not has_titles + ) then + raise exception 'Expected an empty post title, but got a value.'; + end if; + + return new; + end; +$enforce_post_titles$ language plpgsql; + +create trigger insert_enforce_post_titles + before insert on posts + for each row + execute function enforce_post_titles(); + +create trigger update_enforce_post_titles + before update of title on posts + for each row + when (old.title is distinct from new.title) + execute function enforce_post_titles(); diff --git a/src/schema.rs b/src/schema.rs index e3854e3..a38f572 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,3 +1,13 @@ +table! { + posts (id) { + id -> Uuid, + section_id -> Uuid, + title -> Nullable, + publish_date -> Date, + content -> Text, + } +} + table! { refresh_tokens (token) { token -> Bytea, @@ -7,6 +17,16 @@ table! { } } +table! { + sections (id) { + id -> Uuid, + title -> Varchar, + description -> Nullable, + is_default -> Bool, + has_titles -> Bool, + } +} + table! { users (id) { id -> Uuid, @@ -17,6 +37,12 @@ table! { } } +joinable!(posts -> sections (section_id)); joinable!(refresh_tokens -> users (user_id)); -allow_tables_to_appear_in_same_query!(refresh_tokens, users,); +allow_tables_to_appear_in_same_query!( + posts, + refresh_tokens, + sections, + users, +); From 8534090f0f923f48749f2c1bd263bdb5671cb613 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 13 Sep 2021 17:35:06 +0200 Subject: [PATCH 2/5] Wrote some database boilerplate --- src/db/mod.rs | 3 +++ src/db/posts.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/db/sections.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/db/tokens.rs | 4 ++-- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/db/posts.rs create mode 100644 src/db/sections.rs diff --git a/src/db/mod.rs b/src/db/mod.rs index 9c831dd..19cb419 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,5 +1,8 @@ pub mod tokens; pub mod users; +pub mod sections; +pub mod posts; pub use tokens::{NewRefreshToken, RefreshToken}; pub use users::{NewUser, User}; +pub use sections::{Section, NewSection}; diff --git a/src/db/posts.rs b/src/db/posts.rs new file mode 100644 index 0000000..0493405 --- /dev/null +++ b/src/db/posts.rs @@ -0,0 +1,44 @@ +use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; +use uuid::Uuid; +use chrono::NaiveDate; + +use crate::{ + errors::{RbError, RbResult}, + schema::{posts, posts::dsl::*}, +}; + +#[derive(Queryable)] +pub struct Post +{ + pub id: Uuid, + pub section_id: Uuid, + pub title: Option, + pub publish_date: NaiveDate, + pub content: String, +} + +#[derive(Insertable)] +#[table_name = "posts"] +pub struct NewPost +{ + pub section_id: Uuid, + pub title: Option, + pub publish_date: NaiveDate, +} + +pub fn all(conn: &PgConnection) -> RbResult> +{ + posts.load::(conn).map_err(|_| RbError::DbError("Couldn't get all posts.")) +} + +pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult<()> +{ + insert_into(posts) + .values(new_post) + .execute(conn) + .map_err(|_| RbError::DbError("Couldn't insert post."))?; + + // TODO check for conflict? + + Ok(()) +} diff --git a/src/db/sections.rs b/src/db/sections.rs new file mode 100644 index 0000000..cff2736 --- /dev/null +++ b/src/db/sections.rs @@ -0,0 +1,44 @@ +use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; +use uuid::Uuid; + +use crate::{ + errors::{RbError, RbResult}, + schema::{sections, sections::dsl::*}, +}; + +#[derive(Queryable)] +pub struct Section +{ + pub id: Uuid, + pub title: String, + pub description: Option, + pub is_default: bool, + pub has_titles: bool, +} + +#[derive(Insertable)] +#[table_name = "sections"] +pub struct NewSection +{ + title: String, + description: Option, + is_default: bool, + has_titles: bool, +} + +pub fn all(conn: &PgConnection) -> RbResult> +{ + sections.load::
(conn).map_err(|_| RbError::DbError("Couldn't get all sections")) +} + +pub fn create(conn: &PgConnection, new_section: &NewSection) -> RbResult<()> +{ + insert_into(sections) + .values(new_section) + .execute(conn) + .map_err(|_| RbError::DbError("Couldn't insert section."))?; + + // TODO check for conflict? + + Ok(()) +} diff --git a/src/db/tokens.rs b/src/db/tokens.rs index 8940721..97dd02d 100644 --- a/src/db/tokens.rs +++ b/src/db/tokens.rs @@ -36,7 +36,7 @@ pub fn create(conn: &PgConnection, new_refresh_token: &NewRefreshToken) -> RbRes insert_into(refresh_tokens) .values(new_refresh_token) .execute(conn) - .map_err(|_| RbError::Custom("Couldn't insert refresh token."))?; + .map_err(|_| RbError::DbError("Couldn't insert refresh token."))?; // TODO check for conflict? @@ -53,7 +53,7 @@ pub fn find_with_user( .inner_join(crate::schema::users::dsl::users) .filter(token.eq(token_val)) .first::<(RefreshToken, super::users::User)>(conn) - .map_err(|_| RbError::Custom("Couldn't get refresh token & user.")) + .map_err(|_| RbError::DbError("Couldn't get refresh token & user.")) .ok() } From 3e7612a9a8b278a21a561295464aae31f30e5202 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 13 Sep 2021 22:15:38 +0200 Subject: [PATCH 3/5] Added create section endpoint --- Rb.yaml | 21 +++++++++++++++++++++ rustfmt.toml | 2 +- src/db/mod.rs | 6 +++--- src/db/posts.rs | 6 ++++-- src/db/sections.rs | 12 ++++++++---- src/errors.rs | 2 +- src/guards.rs | 18 ++++++++---------- src/main.rs | 2 ++ src/schema.rs | 7 +------ src/sections.rs | 16 ++++++++++++++++ tests/admin.py | 13 ++++++++++--- 11 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 src/sections.rs diff --git a/Rb.yaml b/Rb.yaml index c944758..29a37f8 100644 --- a/Rb.yaml +++ b/Rb.yaml @@ -21,3 +21,24 @@ debug: databases: postgres_rb: url: "postgres://rb:rb@localhost:5432/rb" + +# This config is just used for testing, you should change it when deploying +release: + keep_alive: 5 + read_timeout: 5 + write_timeout: 5 + log_level: "normal" + limits: + forms: 32768 + + admin_user: "admin" + admin_pass: "password" + jwt: + key: "secret" + refresh_token_size: 64 + # Just 5 seconds for debugging + refresh_token_expire: 60 + + databases: + postgres_rb: + url: "postgres://rb:rb@localhost:5432/rb" diff --git a/rustfmt.toml b/rustfmt.toml index 5e52857..8e8627b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -36,7 +36,7 @@ license_template_path = "" make_backup = false match_arm_blocks = true match_arm_leading_pipes = "Never" -match_block_trailing_comma = false +match_block_trailing_comma = true max_width = 100 merge_derives = true newline_style = "Auto" diff --git a/src/db/mod.rs b/src/db/mod.rs index 19cb419..d1dcdcf 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,8 +1,8 @@ +pub mod posts; +pub mod sections; pub mod tokens; pub mod users; -pub mod sections; -pub mod posts; +pub use sections::{NewSection, Section}; pub use tokens::{NewRefreshToken, RefreshToken}; pub use users::{NewUser, User}; -pub use sections::{Section, NewSection}; diff --git a/src/db/posts.rs b/src/db/posts.rs index 0493405..8014f3f 100644 --- a/src/db/posts.rs +++ b/src/db/posts.rs @@ -1,6 +1,6 @@ +use chrono::NaiveDate; use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; use uuid::Uuid; -use chrono::NaiveDate; use crate::{ errors::{RbError, RbResult}, @@ -28,7 +28,9 @@ pub struct NewPost pub fn all(conn: &PgConnection) -> RbResult> { - posts.load::(conn).map_err(|_| RbError::DbError("Couldn't get all posts.")) + posts + .load::(conn) + .map_err(|_| RbError::DbError("Couldn't get all posts.")) } pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult<()> diff --git a/src/db/sections.rs b/src/db/sections.rs index cff2736..19721fb 100644 --- a/src/db/sections.rs +++ b/src/db/sections.rs @@ -1,4 +1,5 @@ use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; +use serde::Deserialize; use uuid::Uuid; use crate::{ @@ -16,19 +17,22 @@ pub struct Section pub has_titles: bool, } -#[derive(Insertable)] +#[derive(Deserialize, Insertable)] #[table_name = "sections"] +// #[serde(rename_all = "camelCase")] pub struct NewSection { title: String, description: Option, - is_default: bool, - has_titles: bool, + is_default: Option, + has_titles: Option, } pub fn all(conn: &PgConnection) -> RbResult> { - sections.load::
(conn).map_err(|_| RbError::DbError("Couldn't get all sections")) + sections + .load::
(conn) + .map_err(|_| RbError::DbError("Couldn't get all sections")) } pub fn create(conn: &PgConnection, new_section: &NewSection) -> RbResult<()> diff --git a/src/errors.rs b/src/errors.rs index bb7856a..1f9aff3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -61,7 +61,7 @@ impl RbError RbError::AuthInvalidRefreshToken => "This refresh token is not valid.", RbError::AuthDuplicateRefreshToken => { "This refresh token has already been used. The user has been blocked." - } + }, RbError::AuthMissingHeader => "Missing Authorization header.", RbError::UMDuplicateUser => "This user already exists.", diff --git a/src/guards.rs b/src/guards.rs index 7b40bdd..3510163 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -10,7 +10,7 @@ use sha2::Sha256; use crate::{auth::jwt::Claims, errors::RbError, RbConfig}; -/// Extracts a "Authorization: Bearer" string from the headers. +/// Extracts an "Authorization: Bearer" string from the headers. pub struct Bearer<'a>(&'a str); #[rocket::async_trait] @@ -22,7 +22,7 @@ impl<'r> FromRequest<'r> for Bearer<'r> { // If the header isn't present, just forward to the next route let header = match req.headers().get_one("Authorization") { - None => return Outcome::Failure((Status::BadRequest, Self::Error::AuthMissingHeader)), + None => return Outcome::Forward(()), Some(val) => val, }; @@ -31,12 +31,10 @@ impl<'r> FromRequest<'r> for Bearer<'r> } // Extract the jwt token from the header - let auth_string = match header.get(7..) { - Some(s) => s, - None => return Outcome::Failure((Status::Unauthorized, Self::Error::AuthUnauthorized)), - }; - - Outcome::Success(Self(auth_string)) + match header.get(7..) { + Some(s) => Outcome::Success(Self(s)), + None => Outcome::Failure((Status::Unauthorized, Self::Error::AuthUnauthorized)), + } } } @@ -63,14 +61,14 @@ impl<'r> FromRequest<'r> for Jwt Status::InternalServerError, Self::Error::Custom("Failed to do Hmac thing."), )) - } + }, }; // Verify token using key let claims: Claims = match bearer.verify_with_key(&key) { Ok(claims) => claims, Err(_) => { return Outcome::Failure((Status::Unauthorized, Self::Error::AuthUnauthorized)) - } + }, }; Outcome::Success(Self(claims)) diff --git a/src/main.rs b/src/main.rs index 2819e79..adb2f26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ pub mod db; pub mod errors; pub mod guards; pub(crate) mod schema; +pub mod sections; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; @@ -111,4 +112,5 @@ fn rocket() -> _ "/api/admin", routes![admin::get_users, admin::create_user, admin::get_user_info], ) + .mount("/api/sections", routes![sections::create_section]) } diff --git a/src/schema.rs b/src/schema.rs index a38f572..45b9813 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -40,9 +40,4 @@ table! { joinable!(posts -> sections (section_id)); joinable!(refresh_tokens -> users (user_id)); -allow_tables_to_appear_in_same_query!( - posts, - refresh_tokens, - sections, - users, -); +allow_tables_to_appear_in_same_query!(posts, refresh_tokens, sections, users,); diff --git a/src/sections.rs b/src/sections.rs new file mode 100644 index 0000000..c5e2f27 --- /dev/null +++ b/src/sections.rs @@ -0,0 +1,16 @@ +use rocket::serde::json::Json; + +use crate::{db, errors::RbResult, guards::Admin, RbDbConn}; + +/// Create a new section. +#[post("/", data = "")] +pub async fn create_section( + _admin: Admin, + conn: RbDbConn, + new_section: Json, +) -> RbResult<()> +{ + Ok(conn + .run(move |c| db::sections::create(c, &new_section.into_inner())) + .await?) +} diff --git a/tests/admin.py b/tests/admin.py index 19a1c5b..069c2dd 100644 --- a/tests/admin.py +++ b/tests/admin.py @@ -2,7 +2,7 @@ import requests class RbClient: - def __init__(self, username, password, base_url = "http://localhost:8000/api"): + def __init__(self, username = "admin", password = "password", base_url = "http://localhost:8000/api"): self.username = username self.password = password self.base_url = base_url @@ -17,6 +17,7 @@ class RbClient: }) if r.status_code != 200: + print(r.text) raise Exception("Couldn't login") res = r.json() @@ -56,9 +57,15 @@ class RbClient: def get(self, url, *args, **kwargs): return self._request("GET", f"{self.base_url}{url}", *args, **kwargs) + def post(self, url, *args, **kwargs): + return self._request("POST", f"{self.base_url}{url}", *args, **kwargs) + if __name__ == "__main__": - client = RbClient("admin", "password") + client = RbClient() - print(client.get("/admin/users").json()) + # print(client.get("/admin/users").json()) + client.post("/sections", json={ + "title": "this is a title" + }) From 6d83c1803609b76a270596f330c50956f3723bb3 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Wed, 15 Sep 2021 11:36:40 +0200 Subject: [PATCH 4/5] Documented entire db section --- src/db/mod.rs | 3 +++ src/db/posts.rs | 16 ++++++++++++++++ src/db/sections.rs | 15 +++++++++++++++ src/db/tokens.rs | 32 +++++++++++++++++++++++++++++++ src/db/users.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++ src/sections.rs | 10 +++++++++- 6 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/db/mod.rs b/src/db/mod.rs index d1dcdcf..35e4995 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,3 +1,6 @@ +//! The db module contains all Diesel-related logic. This is to prevent the various Diesel imports +//! from poluting other modules' namespaces. + pub mod posts; pub mod sections; pub mod tokens; diff --git a/src/db/posts.rs b/src/db/posts.rs index 8014f3f..2486c08 100644 --- a/src/db/posts.rs +++ b/src/db/posts.rs @@ -1,3 +1,5 @@ +//! Handles all posts-related database operations. + use chrono::NaiveDate; use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; use uuid::Uuid; @@ -7,6 +9,7 @@ use crate::{ schema::{posts, posts::dsl::*}, }; +/// Represents a post contained within the database. #[derive(Queryable)] pub struct Post { @@ -17,6 +20,7 @@ pub struct Post pub content: String, } +/// Represents a new post to be added to the database. #[derive(Insertable)] #[table_name = "posts"] pub struct NewPost @@ -26,6 +30,12 @@ pub struct NewPost pub publish_date: NaiveDate, } +/// Returns all posts in the database; should be used with care as this method could quickly return +/// a large amount of data. +/// +/// # Arguments +/// +/// * `conn` - a reference to a database connection pub fn all(conn: &PgConnection) -> RbResult> { posts @@ -33,6 +43,12 @@ pub fn all(conn: &PgConnection) -> RbResult> .map_err(|_| RbError::DbError("Couldn't get all posts.")) } +/// Insert a new post into the database. +/// +/// # Arguments +/// +/// * `conn` - reference to a database connection +/// * `new_post` - the new post object to insert pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult<()> { insert_into(posts) diff --git a/src/db/sections.rs b/src/db/sections.rs index 19721fb..ca8956b 100644 --- a/src/db/sections.rs +++ b/src/db/sections.rs @@ -1,3 +1,5 @@ +//! Handles all section-related database operations. + use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; use serde::Deserialize; use uuid::Uuid; @@ -7,6 +9,7 @@ use crate::{ schema::{sections, sections::dsl::*}, }; +/// Represents a section contained in the database. #[derive(Queryable)] pub struct Section { @@ -17,6 +20,7 @@ pub struct Section pub has_titles: bool, } +/// A new section to be added into the database. #[derive(Deserialize, Insertable)] #[table_name = "sections"] // #[serde(rename_all = "camelCase")] @@ -28,6 +32,11 @@ pub struct NewSection has_titles: Option, } +/// Returns all sections in the database. +/// +/// # Arguments +/// +/// * `conn` - reference to a database connection pub fn all(conn: &PgConnection) -> RbResult> { sections @@ -35,6 +44,12 @@ pub fn all(conn: &PgConnection) -> RbResult> .map_err(|_| RbError::DbError("Couldn't get all sections")) } +/// Inserts a new section into the database. +/// +/// # Arguments +/// +/// * `conn` - reference to a database connection +/// * `new_section` - the new section to be added pub fn create(conn: &PgConnection, new_section: &NewSection) -> RbResult<()> { insert_into(sections) diff --git a/src/db/tokens.rs b/src/db/tokens.rs index 97dd02d..cbb8898 100644 --- a/src/db/tokens.rs +++ b/src/db/tokens.rs @@ -1,3 +1,5 @@ +//! Handles refresh token-related database operations. + use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; use uuid::Uuid; @@ -6,6 +8,7 @@ use crate::{ schema::{refresh_tokens, refresh_tokens::dsl::*}, }; +/// A refresh token as stored in the database #[derive(Queryable)] pub struct RefreshToken { @@ -15,6 +18,7 @@ pub struct RefreshToken pub last_used_at: Option, } +/// A new refresh token to be added into the database #[derive(Insertable)] #[table_name = "refresh_tokens"] pub struct NewRefreshToken @@ -24,6 +28,12 @@ pub struct NewRefreshToken pub expires_at: chrono::NaiveDateTime, } +// TODO add pagination as this could grow very quickly +/// Returns all refresh tokens contained in the database. +/// +/// # Arguments +/// +/// * `conn` - database connection to use pub fn all(conn: &PgConnection) -> RbResult> { refresh_tokens @@ -31,6 +41,12 @@ pub fn all(conn: &PgConnection) -> RbResult> .map_err(|_| RbError::DbError("Couldn't get all refresh tokens.")) } +/// Insert a new refresh token into the database. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `new_refresh_token` - token to insert pub fn create(conn: &PgConnection, new_refresh_token: &NewRefreshToken) -> RbResult<()> { insert_into(refresh_tokens) @@ -43,6 +59,12 @@ pub fn create(conn: &PgConnection, new_refresh_token: &NewRefreshToken) -> RbRes Ok(()) } +/// Returns the token & user data associated with the given refresh token value. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `token_val` - token value to search for pub fn find_with_user( conn: &PgConnection, token_val: &[u8], @@ -57,6 +79,16 @@ pub fn find_with_user( .ok() } +/// Updates a token's `last_used_at` column value. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `token_` - value of the refresh token to update +/// * `last_used_at_` - date value to update column with +/// +/// **NOTE**: argument names use trailing underscores as to not conflict with Diesel's imported dsl +/// names. pub fn update_last_used_at( conn: &PgConnection, token_: &[u8], diff --git a/src/db/users.rs b/src/db/users.rs index efc74db..37ef9c2 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -1,3 +1,5 @@ +//! Handles user-related database operations. + use diesel::{prelude::*, AsChangeset, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -7,6 +9,7 @@ use crate::{ schema::{users, users::dsl::*}, }; +/// A user as stored in the database. #[derive(Queryable, Serialize)] pub struct User { @@ -18,6 +21,7 @@ pub struct User pub admin: bool, } +/// A new user to add to the database. #[derive(Insertable, AsChangeset, Deserialize)] #[table_name = "users"] pub struct NewUser @@ -27,6 +31,11 @@ pub struct NewUser pub admin: bool, } +/// Returns all users in the database. +/// +/// # Arguments +/// +/// * `conn` - database connection to use pub fn all(conn: &PgConnection) -> RbResult> { users @@ -34,11 +43,23 @@ pub fn all(conn: &PgConnection) -> RbResult> .map_err(|_| RbError::DbError("Couldn't get all users.")) } +/// Find a user with a given ID. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `user_id` - ID to search for pub fn find(conn: &PgConnection, user_id: Uuid) -> Option { users.find(user_id).first::(conn).ok() } +/// Find a user with a given username. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `username_` - username to search for pub fn find_by_username(conn: &PgConnection, username_: &str) -> RbResult { Ok(users @@ -47,6 +68,12 @@ pub fn find_by_username(conn: &PgConnection, username_: &str) -> RbResult .map_err(|_| RbError::DbError("Couldn't find users by username."))?) } +/// Insert a new user into the database +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `new_user` - user to insert pub fn create(conn: &PgConnection, new_user: &NewUser) -> RbResult<()> { let count = diesel::insert_into(users) @@ -61,6 +88,12 @@ pub fn create(conn: &PgConnection, new_user: &NewUser) -> RbResult<()> Ok(()) } +/// Either create a new user or update an existing one on conflict. +/// +/// # Arguments +/// +/// * `conn` - database connection to use +/// * `new_user` - user to insert/update pub fn create_or_update(conn: &PgConnection, new_user: &NewUser) -> RbResult<()> { diesel::insert_into(users) @@ -74,6 +107,12 @@ pub fn create_or_update(conn: &PgConnection, new_user: &NewUser) -> RbResult<()> Ok(()) } +/// Delete the user with the given ID. +/// +/// # Arguments +/// +/// `conn` - database connection to use +/// `user_id` - ID of user to delete pub fn delete(conn: &PgConnection, user_id: Uuid) -> RbResult<()> { diesel::delete(users.filter(id.eq(user_id))) @@ -83,6 +122,14 @@ pub fn delete(conn: &PgConnection, user_id: Uuid) -> RbResult<()> Ok(()) } +/// Block a user given an ID. +/// In practice, this means updating the user's entry so that the `blocked` column is set to +/// `true`. +/// +/// # Arguments +/// +/// `conn` - database connection to use +/// `user_id` - ID of user to block pub fn block(conn: &PgConnection, user_id: Uuid) -> RbResult<()> { diesel::update(users.filter(id.eq(user_id))) diff --git a/src/sections.rs b/src/sections.rs index c5e2f27..e4def24 100644 --- a/src/sections.rs +++ b/src/sections.rs @@ -1,8 +1,16 @@ +//! This module handles management of site sections (aka blogs). + use rocket::serde::json::Json; use crate::{db, errors::RbResult, guards::Admin, RbDbConn}; -/// Create a new section. +/// Route for creating a new section. +/// +/// # Arguments +/// +/// * `_admin` - guard ensuring user is admin +/// * `conn` - guard providing a connection to the database +/// * `new_section` - Json-encoded NewSection object #[post("/", data = "")] pub async fn create_section( _admin: Admin, From 548dd0d022da69667111d3c89be2fe1c3348ada0 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 23 Sep 2021 15:30:39 +0200 Subject: [PATCH 5/5] Tried to add MirageJS but failed --- src/db/posts.rs | 4 - src/db/sections.rs | 2 +- web/package.json | 4 +- web/src/components/MirageTest.svelte | 19 + web/src/components/SvelteCounter.svelte | 2 +- web/src/layouts/index.astro | 4 + web/src/pages/index.astro | 23 + web/yarn.lock | 996 +++++++++++++++--------- 8 files changed, 681 insertions(+), 373 deletions(-) create mode 100644 web/src/components/MirageTest.svelte diff --git a/src/db/posts.rs b/src/db/posts.rs index 2486c08..163902e 100644 --- a/src/db/posts.rs +++ b/src/db/posts.rs @@ -1,5 +1,3 @@ -//! Handles all posts-related database operations. - use chrono::NaiveDate; use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable}; use uuid::Uuid; @@ -9,7 +7,6 @@ use crate::{ schema::{posts, posts::dsl::*}, }; -/// Represents a post contained within the database. #[derive(Queryable)] pub struct Post { @@ -20,7 +17,6 @@ pub struct Post pub content: String, } -/// Represents a new post to be added to the database. #[derive(Insertable)] #[table_name = "posts"] pub struct NewPost diff --git a/src/db/sections.rs b/src/db/sections.rs index ca8956b..b429c85 100644 --- a/src/db/sections.rs +++ b/src/db/sections.rs @@ -23,7 +23,7 @@ pub struct Section /// A new section to be added into the database. #[derive(Deserialize, Insertable)] #[table_name = "sections"] -// #[serde(rename_all = "camelCase")] +#[serde(rename_all = "camelCase")] pub struct NewSection { title: String, diff --git a/web/package.json b/web/package.json index 6dfbfa6..b1070aa 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,9 @@ "build": "astro build" }, "devDependencies": { + "@astrojs/renderer-svelte": "^0.1.1", "astro": "0.19.0-next.2", - "@astrojs/renderer-svelte": "^0.1.1" + "miragejs": "^0.1.41", + "typescript": "^4.4.3" } } diff --git a/web/src/components/MirageTest.svelte b/web/src/components/MirageTest.svelte new file mode 100644 index 0000000..ac3676b --- /dev/null +++ b/web/src/components/MirageTest.svelte @@ -0,0 +1,19 @@ + + + diff --git a/web/src/components/SvelteCounter.svelte b/web/src/components/SvelteCounter.svelte index f493c25..ea6cf52 100644 --- a/web/src/components/SvelteCounter.svelte +++ b/web/src/components/SvelteCounter.svelte @@ -1,4 +1,4 @@ -