Switch DB layer to Diesel from SeaORM and Fjall #2
20 changed files with 117 additions and 139 deletions
|
@ -2,8 +2,8 @@ use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use pique::db::establish_connection;
|
use pique::db::establish_connection;
|
||||||
use pique::models::users::{self, NewUser};
|
use pique::models::users::{self, NewUser};
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use rand::{distributions::DistString, thread_rng};
|
use rand::thread_rng;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() -> Result<()> {
|
pub async fn main() -> Result<()> {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use pique::server;
|
use pique::server;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
58
src/db.rs
58
src/db.rs
|
@ -1,11 +1,8 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
use diesel::r2d2::Pool;
|
|
||||||
use diesel::SqliteConnection;
|
use diesel::SqliteConnection;
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||||
|
|
||||||
use crate::{password, prelude::*};
|
|
||||||
|
|
||||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
/// Establishes a connection to the database using the given URL.
|
/// 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.
|
/// * `url` - The database URL to connect to.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # 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 {
|
pub fn establish_connection(url: &str) -> SqliteConnection {
|
||||||
SqliteConnection::establish(url).unwrap_or_else(|_| panic!("Error connecting to {}", url))
|
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) {
|
pub fn migrate(conn: &mut SqliteConnection) {
|
||||||
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ pub mod projects;
|
||||||
|
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
pub use login::login_page;
|
pub use login::{login_page, login_submit};
|
||||||
pub use login::login_submit;
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub fn internal_server_error() -> Response {
|
pub fn internal_server_error() -> Response {
|
||||||
|
|
|
@ -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 axum_login::AuthSession;
|
||||||
|
|
||||||
use crate::{
|
use crate::handler::internal_error;
|
||||||
handler::internal_error,
|
use crate::models::documents::{self, NewDocument};
|
||||||
models::{
|
use crate::models::users::User;
|
||||||
documents::{self, NewDocument},
|
use crate::permissions::query::Permission;
|
||||||
users::User,
|
use crate::permissions::{self};
|
||||||
},
|
use crate::prelude::*;
|
||||||
permissions::{self, query::Permission},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn documents_page(
|
pub async fn documents_page(
|
||||||
State(provider): State<Provider>,
|
State(provider): State<Provider>,
|
||||||
|
|
|
@ -2,8 +2,8 @@ use axum::response::Redirect;
|
||||||
use axum_login::AuthSession;
|
use axum_login::AuthSession;
|
||||||
|
|
||||||
use crate::models::projects::Project;
|
use crate::models::projects::Project;
|
||||||
|
use crate::permissions;
|
||||||
use {crate::permissions, crate::prelude::*};
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub async fn home_page(
|
pub async fn home_page(
|
||||||
State(provider): State<Provider>,
|
State(provider): State<Provider>,
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use axum::{response::Redirect, Form};
|
use axum::response::Redirect;
|
||||||
|
use axum::Form;
|
||||||
use axum_login::AuthSession;
|
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 struct LoginTemplate {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
|
@ -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 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 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(
|
pub async fn projects_page(
|
||||||
State(provider): State<Provider>,
|
State(provider): State<Provider>,
|
||||||
|
|
|
@ -12,3 +12,4 @@ pub mod serialize;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
pub mod validation;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::schema::documents::dsl;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::DbError;
|
use super::DbError;
|
||||||
|
use crate::schema::documents::dsl;
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
||||||
#[diesel(table_name = crate::schema::documents)]
|
#[diesel(table_name = crate::schema::documents)]
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use diesel::{expression::AsExpression, prelude::*, sql_types::Text};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use diesel::expression::AsExpression;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::sql_types::Text;
|
||||||
|
|
||||||
#[derive(AsExpression, Debug, Clone)]
|
#[derive(AsExpression, Debug, Clone)]
|
||||||
#[diesel(sql_type = Text)]
|
#[diesel(sql_type = Text)]
|
||||||
pub enum ProjectRole {
|
pub enum ProjectRole {
|
||||||
|
@ -63,9 +66,10 @@ pub struct NewProjectMembership {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod query {
|
pub mod query {
|
||||||
use super::*;
|
|
||||||
use diesel::SqliteConnection;
|
use diesel::SqliteConnection;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
db: &mut SqliteConnection,
|
db: &mut SqliteConnection,
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::schema::projects::dsl;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::DbError;
|
use super::DbError;
|
||||||
|
use crate::schema::projects::dsl;
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
||||||
#[diesel(table_name = crate::schema::projects)]
|
#[diesel(table_name = crate::schema::projects)]
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::schema::users::dsl;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::password;
|
|
||||||
|
|
||||||
use super::DbError;
|
use super::DbError;
|
||||||
|
use crate::db::ValidationError;
|
||||||
|
use crate::password;
|
||||||
|
use crate::schema::users::dsl;
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
||||||
#[diesel(table_name = crate::schema::users)]
|
#[diesel(table_name = crate::schema::users)]
|
||||||
|
@ -40,6 +39,28 @@ impl NewUser {
|
||||||
password_hash,
|
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> {
|
pub struct Query<'a> {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
pub mod query {
|
pub mod query {
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::SqliteConnection;
|
||||||
|
|
||||||
use crate::models::documents::Document;
|
use crate::models::documents::Document;
|
||||||
use crate::models::project_memberships::ProjectRole;
|
use crate::models::project_memberships::ProjectRole;
|
||||||
use crate::models::projects::Project;
|
use crate::models::projects::Project;
|
||||||
use diesel::prelude::*;
|
|
||||||
use diesel::SqliteConnection;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Permission {
|
pub enum Permission {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
pub use crate::provider::Provider;
|
|
||||||
pub use axum::extract::State;
|
pub use axum::extract::State;
|
||||||
pub use axum::response::{Html, IntoResponse, Response};
|
pub use axum::response::{Html, IntoResponse, Response};
|
||||||
pub use minijinja::context;
|
pub use minijinja::context;
|
||||||
pub use serde::{Deserialize, Serialize};
|
pub use serde::{Deserialize, Serialize};
|
||||||
pub use tracing::{debug, error, info, warn};
|
pub use tracing::{debug, error, info, warn};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use crate::provider::Provider;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use diesel::{
|
|
||||||
r2d2::{ConnectionManager, Pool},
|
|
||||||
SqliteConnection,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
|
use diesel::SqliteConnection;
|
||||||
|
use minijinja_autoreload::AutoReloader;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use minijinja_autoreload::AutoReloader;
|
use crate::handler::internal_server_error;
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::{handler::internal_server_error, prelude::*};
|
|
||||||
|
|
||||||
pub type ConnectionPool = Pool<ConnectionManager<SqliteConnection>>;
|
pub type ConnectionPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||||
pub type PooledConnection = diesel::r2d2::PooledConnection<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> {
|
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 env = self.template_loader.acquire_env().unwrap();
|
||||||
let template = env.get_template(path)?;
|
let template = env.get_template(path)?;
|
||||||
let rendered = template.render(data)?;
|
let rendered = template.render(data)?;
|
||||||
|
|
|
@ -1,38 +1,31 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::{
|
use axum::routing::{get, post};
|
||||||
routing::{get, post},
|
use axum::Router;
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use axum_login::AuthManagerLayerBuilder;
|
use axum_login::AuthManagerLayerBuilder;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations};
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations};
|
||||||
use tower_http::{
|
use tower_http::services::ServeDir;
|
||||||
services::ServeDir,
|
use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer};
|
||||||
trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer},
|
|
||||||
};
|
|
||||||
use tower_sessions::SessionManagerLayer;
|
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 tracing::Level;
|
||||||
|
|
||||||
use crate::{
|
use crate::config::CommandLineOptions;
|
||||||
config::CommandLineOptions,
|
use crate::db;
|
||||||
provider::Provider,
|
use crate::handler::documents::{
|
||||||
db,
|
create_document_page, create_document_submit, documents_page, edit_document_page,
|
||||||
handler::{
|
edit_document_submit,
|
||||||
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::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/");
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/");
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||||
|
|
||||||
use crate::{
|
use crate::models::{self, users, DbError};
|
||||||
models::{self, users, DbError},
|
use crate::password;
|
||||||
password,
|
use crate::prelude::*;
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use free_icons::IconAttrs;
|
use free_icons::IconAttrs;
|
||||||
use minijinja::{path_loader, Environment};
|
use minijinja::{path_loader, Environment, Error, ErrorKind};
|
||||||
use minijinja::{Error, ErrorKind};
|
|
||||||
use minijinja_autoreload::AutoReloader;
|
use minijinja_autoreload::AutoReloader;
|
||||||
|
|
||||||
pub fn make_template_loader(auto_reload: bool) -> AutoReloader {
|
pub fn make_template_loader(auto_reload: bool) -> AutoReloader {
|
||||||
|
|
16
src/validation.rs
Normal file
16
src/validation.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue