diff --git a/src/bin/admin.rs b/src/bin/admin.rs index 1320e59..e29470b 100644 --- a/src/bin/admin.rs +++ b/src/bin/admin.rs @@ -2,8 +2,8 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use pique::db::establish_connection; use pique::models::users::{self, NewUser}; -use rand::distributions::Alphanumeric; -use rand::{distributions::DistString, thread_rng}; +use rand::distributions::{Alphanumeric, DistString}; +use rand::thread_rng; #[tokio::main] pub async fn main() -> Result<()> { diff --git a/src/bin/pique.rs b/src/bin/pique.rs index 7c3bd81..b965a13 100644 --- a/src/bin/pique.rs +++ b/src/bin/pique.rs @@ -1,5 +1,4 @@ use anyhow::Result; - use pique::server; #[tokio::main] diff --git a/src/db.rs b/src/db.rs index ae6ce79..f8131e0 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,11 +1,8 @@ use diesel::prelude::*; -use diesel::r2d2::ConnectionManager; -use diesel::r2d2::Pool; +use diesel::r2d2::{ConnectionManager, Pool}; use diesel::SqliteConnection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use crate::{password, prelude::*}; - pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); /// Establishes a connection to the database using the given URL. @@ -14,7 +11,8 @@ pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); /// * `url` - The database URL to connect to. /// /// # Panics -/// Panics if the database URL is not set or if the connection cannot be established. +/// Panics if the database URL is not set or if the connection cannot be +/// established. pub fn establish_connection(url: &str) -> SqliteConnection { SqliteConnection::establish(url).unwrap_or_else(|_| panic!("Error connecting to {}", url)) } @@ -45,53 +43,3 @@ pub fn build_connection_pool(url: &str) -> Pool Result<(), Vec> { - let mut validation_errors = vec![]; - - if self.full_name.len() > 100 { - validation_errors.push(ValidationError::on("full_name", "too long (max=100)")); - } - - if self.email.len() > 100 { - validation_errors.push(ValidationError::on("email", "too long (max=100)")); - } - - if self.username.len() > 100 { - validation_errors.push(ValidationError::on("username", "too long (max=32)")); - } - - if validation_errors.is_empty() { - Ok(()) - } else { - Err(validation_errors) - } - } - - pub fn hash_password(&self) -> String { - password::hash(&self.password) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ValidationError { - pub field: String, - pub message: String, -} - -impl ValidationError { - pub fn on(field: &str, message: &str) -> ValidationError { - ValidationError { - field: field.to_owned(), - message: message.to_owned(), - } - } -} diff --git a/src/handler.rs b/src/handler.rs index 3fc6bc2..9653742 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -5,8 +5,7 @@ pub mod projects; use axum::http::StatusCode; use axum::response::Response; -pub use login::login_page; -pub use login::login_submit; +pub use login::{login_page, login_submit}; use tracing::error; pub fn internal_server_error() -> Response { diff --git a/src/handler/documents.rs b/src/handler/documents.rs index d4d11d8..16dfff6 100644 --- a/src/handler/documents.rs +++ b/src/handler/documents.rs @@ -1,15 +1,15 @@ -use axum::{extract::Path, http::StatusCode, response::Redirect, Form}; +use axum::extract::Path; +use axum::http::StatusCode; +use axum::response::Redirect; +use axum::Form; use axum_login::AuthSession; -use crate::{ - handler::internal_error, - models::{ - documents::{self, NewDocument}, - users::User, - }, - permissions::{self, query::Permission}, - prelude::*, -}; +use crate::handler::internal_error; +use crate::models::documents::{self, NewDocument}; +use crate::models::users::User; +use crate::permissions::query::Permission; +use crate::permissions::{self}; +use crate::prelude::*; pub async fn documents_page( State(provider): State, diff --git a/src/handler/home.rs b/src/handler/home.rs index f25fef2..f7bfc3d 100644 --- a/src/handler/home.rs +++ b/src/handler/home.rs @@ -2,8 +2,8 @@ use axum::response::Redirect; use axum_login::AuthSession; use crate::models::projects::Project; - -use {crate::permissions, crate::prelude::*}; +use crate::permissions; +use crate::prelude::*; pub async fn home_page( State(provider): State, diff --git a/src/handler/login.rs b/src/handler/login.rs index eebdb69..6dbd8e9 100644 --- a/src/handler/login.rs +++ b/src/handler/login.rs @@ -1,7 +1,10 @@ -use axum::{response::Redirect, Form}; +use axum::response::Redirect; +use axum::Form; use axum_login::AuthSession; -use crate::{handler::internal_server_error, prelude::*, session::Credentials}; +use crate::handler::internal_server_error; +use crate::prelude::*; +use crate::session::Credentials; pub struct LoginTemplate { pub username: String, diff --git a/src/handler/projects.rs b/src/handler/projects.rs index 2103145..2c1884c 100644 --- a/src/handler/projects.rs +++ b/src/handler/projects.rs @@ -1,18 +1,15 @@ -use axum::{http::StatusCode, response::Redirect, Form}; +use axum::http::StatusCode; +use axum::response::Redirect; +use axum::Form; use axum_login::AuthSession; -use crate::{ - handler::internal_server_error, - models::{ - project_memberships::{self, ProjectRole}, - projects::{self, NewProject}, - users::User, - }, - permissions, - prelude::*, -}; - use super::internal_error; +use crate::handler::internal_server_error; +use crate::models::project_memberships::{self, ProjectRole}; +use crate::models::projects::{self, NewProject}; +use crate::models::users::User; +use crate::permissions; +use crate::prelude::*; pub async fn projects_page( State(provider): State, diff --git a/src/lib.rs b/src/lib.rs index ecc092d..1741c25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,4 @@ pub mod serialize; pub mod server; pub mod session; pub mod templates; +pub mod validation; diff --git a/src/models/documents.rs b/src/models/documents.rs index 5a710f3..a49050a 100644 --- a/src/models/documents.rs +++ b/src/models/documents.rs @@ -1,10 +1,9 @@ use diesel::prelude::*; use serde::Serialize; - -use crate::schema::documents::dsl; use uuid::Uuid; use super::DbError; +use crate::schema::documents::dsl; #[derive(Queryable, Selectable, Debug, Clone, Serialize)] #[diesel(table_name = crate::schema::documents)] diff --git a/src/models/project_memberships.rs b/src/models/project_memberships.rs index bb3b231..19922c5 100644 --- a/src/models/project_memberships.rs +++ b/src/models/project_memberships.rs @@ -1,6 +1,9 @@ -use diesel::{expression::AsExpression, prelude::*, sql_types::Text}; use std::fmt; +use diesel::expression::AsExpression; +use diesel::prelude::*; +use diesel::sql_types::Text; + #[derive(AsExpression, Debug, Clone)] #[diesel(sql_type = Text)] pub enum ProjectRole { @@ -63,9 +66,10 @@ pub struct NewProjectMembership { } pub mod query { - use super::*; use diesel::SqliteConnection; + use super::*; + pub fn create( db: &mut SqliteConnection, user_id: &str, diff --git a/src/models/projects.rs b/src/models/projects.rs index c7ba9cd..20f630c 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -1,10 +1,9 @@ use diesel::prelude::*; use serde::Serialize; - -use crate::schema::projects::dsl; use uuid::Uuid; use super::DbError; +use crate::schema::projects::dsl; #[derive(Queryable, Selectable, Debug, Clone, Serialize)] #[diesel(table_name = crate::schema::projects)] diff --git a/src/models/users.rs b/src/models/users.rs index 8669887..40d97f9 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,12 +1,11 @@ use diesel::prelude::*; use serde::Serialize; - -use crate::schema::users::dsl; use uuid::Uuid; -use crate::password; - use super::DbError; +use crate::db::ValidationError; +use crate::password; +use crate::schema::users::dsl; #[derive(Queryable, Selectable, Debug, Clone, Serialize)] #[diesel(table_name = crate::schema::users)] @@ -40,6 +39,28 @@ impl NewUser { password_hash, } } + + pub fn validate(&self) -> Result<(), Vec> { + let mut validation_errors = vec![]; + + if self.name.len() > 100 { + validation_errors.push(ValidationError::on("name", "too long (max=100)")); + } + + if self.email.len() > 100 { + validation_errors.push(ValidationError::on("email", "too long (max=100)")); + } + + if self.username.len() > 32 { + validation_errors.push(ValidationError::on("username", "too long (max=32)")); + } + + if validation_errors.is_empty() { + Ok(()) + } else { + Err(validation_errors) + } + } } pub struct Query<'a> { diff --git a/src/permissions.rs b/src/permissions.rs index 166019c..6cd7f28 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -1,9 +1,10 @@ pub mod query { + use diesel::prelude::*; + use diesel::SqliteConnection; + use crate::models::documents::Document; use crate::models::project_memberships::ProjectRole; use crate::models::projects::Project; - use diesel::prelude::*; - use diesel::SqliteConnection; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Permission { diff --git a/src/prelude.rs b/src/prelude.rs index 9074208..d693cb9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,8 @@ -pub use crate::provider::Provider; pub use axum::extract::State; pub use axum::response::{Html, IntoResponse, Response}; pub use minijinja::context; pub use serde::{Deserialize, Serialize}; pub use tracing::{debug, error, info, warn}; pub use uuid::Uuid; + +pub use crate::provider::Provider; diff --git a/src/provider.rs b/src/provider.rs index 268ab58..677d416 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,13 +1,12 @@ -use diesel::{ - r2d2::{ConnectionManager, Pool}, - SqliteConnection, -}; use std::sync::Arc; + +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::SqliteConnection; +use minijinja_autoreload::AutoReloader; use thiserror::Error; -use minijinja_autoreload::AutoReloader; - -use crate::{handler::internal_server_error, prelude::*}; +use crate::handler::internal_server_error; +use crate::prelude::*; pub type ConnectionPool = Pool>; pub type PooledConnection = diesel::r2d2::PooledConnection>; @@ -38,7 +37,8 @@ impl Provider { } pub fn render(&self, path: &str, data: T) -> anyhow::Result { - // TODO: more graceful handling of the potential errors here; this should not use anyhow + // TODO: more graceful handling of the potential errors here; this should not + // use anyhow let env = self.template_loader.acquire_env().unwrap(); let template = env.get_template(path)?; let rendered = template.render(data)?; diff --git a/src/server.rs b/src/server.rs index dd4c2bd..873ecbd 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,38 +1,31 @@ use std::str::FromStr; use anyhow::Result; -use axum::{ - routing::{get, post}, - Router, -}; +use axum::routing::{get, post}; +use axum::Router; use axum_login::AuthManagerLayerBuilder; use clap::Parser; use diesel_migrations::{embed_migrations, EmbeddedMigrations}; -use tower_http::{ - services::ServeDir, - trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}, -}; +use tower_http::services::ServeDir; +use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; use tower_sessions::SessionManagerLayer; -use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore}; +use tower_sessions_sqlx_store::sqlx::SqlitePool; +use tower_sessions_sqlx_store::SqliteStore; use tracing::Level; -use crate::{ - config::CommandLineOptions, - provider::Provider, - db, - handler::{ - documents::{ - create_document_page, create_document_submit, documents_page, edit_document_page, - edit_document_submit, - }, - home::home_page, - login::logout, - login_page, login_submit, - projects::{create_project_page, create_project_submit, projects_page}, - }, - logging::setup_logging, - templates::make_template_loader, +use crate::config::CommandLineOptions; +use crate::db; +use crate::handler::documents::{ + create_document_page, create_document_submit, documents_page, edit_document_page, + edit_document_submit, }; +use crate::handler::home::home_page; +use crate::handler::login::logout; +use crate::handler::projects::{create_project_page, create_project_submit, projects_page}; +use crate::handler::{login_page, login_submit}; +use crate::logging::setup_logging; +use crate::provider::Provider; +use crate::templates::make_template_loader; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/"); diff --git a/src/session.rs b/src/session.rs index f6f5c27..5b08043 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,11 +1,9 @@ use async_trait::async_trait; use axum_login::{AuthUser, AuthnBackend, UserId}; -use crate::{ - models::{self, users, DbError}, - password, - prelude::*, -}; +use crate::models::{self, users, DbError}; +use crate::password; +use crate::prelude::*; #[derive(Serialize, Deserialize)] pub struct Credentials { diff --git a/src/templates.rs b/src/templates.rs index bd18de7..ac08745 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,6 +1,5 @@ use free_icons::IconAttrs; -use minijinja::{path_loader, Environment}; -use minijinja::{Error, ErrorKind}; +use minijinja::{path_loader, Environment, Error, ErrorKind}; use minijinja_autoreload::AutoReloader; pub fn make_template_loader(auto_reload: bool) -> AutoReloader { diff --git a/src/validation.rs b/src/validation.rs new file mode 100644 index 0000000..7eb4264 --- /dev/null +++ b/src/validation.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ValidationError { + pub field: String, + pub message: String, +} + +impl ValidationError { + pub fn on(field: &str, message: &str) -> ValidationError { + ValidationError { + field: field.to_owned(), + message: message.to_owned(), + } + } +}