This commit is contained in:
Nicole Tietz-Sokolskaya 2024-06-02 11:02:57 -04:00
parent c848037dcb
commit 3bf0f8de74
20 changed files with 117 additions and 139 deletions

View file

@ -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<()> {

View file

@ -1,5 +1,4 @@
use anyhow::Result;
use pique::server;
#[tokio::main]

View file

@ -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<ConnectionManager<SqliteConnecti
pub fn migrate(conn: &mut SqliteConnection) {
conn.run_pending_migrations(MIGRATIONS).unwrap();
}
pub struct NewUser {
pub full_name: String,
pub email: String,
pub username: String,
pub password: String,
}
impl NewUser {
pub fn validate(&self) -> Result<(), Vec<ValidationError>> {
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(),
}
}
}

View file

@ -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 {

View file

@ -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<Provider>,

View file

@ -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<Provider>,

View file

@ -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,

View file

@ -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<Provider>,

View file

@ -12,3 +12,4 @@ pub mod serialize;
pub mod server;
pub mod session;
pub mod templates;
pub mod validation;

View file

@ -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)]

View file

@ -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,

View file

@ -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)]

View file

@ -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<ValidationError>> {
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> {

View file

@ -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 {

View file

@ -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;

View file

@ -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<ConnectionManager<SqliteConnection>>;
pub type PooledConnection = diesel::r2d2::PooledConnection<ConnectionManager<SqliteConnection>>;
@ -38,7 +37,8 @@ impl Provider {
}
pub fn render<T: Serialize>(&self, path: &str, data: T) -> anyhow::Result<String> {
// 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)?;

View file

@ -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/");

View file

@ -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 {

View file

@ -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 {

16
src/validation.rs Normal file
View file

@ -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(),
}
}
}