Switch DB layer to Diesel from SeaORM and Fjall #2
20 changed files with 234 additions and 457 deletions
136
Cargo.lock
generated
136
Cargo.lock
generated
|
@ -677,16 +677,6 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-skiplist"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
|
@ -891,12 +881,6 @@ version = "0.15.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "double-ended-peekable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57"
|
||||
|
||||
[[package]]
|
||||
name = "dsl_auto_type"
|
||||
version = "0.1.0"
|
||||
|
@ -1056,22 +1040,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fjall"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa8b3cbbdfa775c311965846c523ae291327a5cc3e433479583922ff9527594"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
"fs_extra",
|
||||
"log",
|
||||
"lsm-tree",
|
||||
"path-absolutize",
|
||||
"std-semaphore",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
|
@ -1134,12 +1102,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
@ -1295,12 +1257,6 @@ version = "0.29.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "guardian"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6817154789d2e9bb2af0486500e774af579d0e6539247044f06d803b141448b5"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
|
@ -1721,38 +1677,6 @@ version = "0.4.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "lsm-tree"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "792f0f9d75b518035f7247774580ba60ee211d381237946e1a40609433f5573f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc32fast",
|
||||
"crossbeam-skiplist",
|
||||
"double-ended-peekable",
|
||||
"fs_extra",
|
||||
"guardian",
|
||||
"log",
|
||||
"lz4_flex",
|
||||
"path-absolutize",
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
|
||||
dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
|
@ -1924,14 +1848,6 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "model_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.7"
|
||||
|
@ -2188,24 +2104,6 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
|
||||
dependencies = [
|
||||
"path-dedot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-dedot"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
|
@ -2356,11 +2254,9 @@ dependencies = [
|
|||
"diesel_migrations",
|
||||
"dotenvy",
|
||||
"env_logger",
|
||||
"fjall",
|
||||
"free-icons",
|
||||
"minijinja",
|
||||
"minijinja-autoreload",
|
||||
"model_derive",
|
||||
"rand 0.8.5",
|
||||
"redb",
|
||||
"serde",
|
||||
|
@ -2465,16 +2361,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick_cache"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "347e1a588d1de074eeb3c00eadff93db4db65aeb62aee852b1efd0949fe65b6c"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
|
@ -3282,18 +3168,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "std-semaphore"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.5"
|
||||
|
@ -3764,16 +3638,6 @@ version = "0.1.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90"
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
|
|
@ -20,11 +20,9 @@ diesel = { version = "2.2.0", features = ["extras", "returning_clauses_for_sqlit
|
|||
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
|
||||
dotenvy = "0.15.7"
|
||||
env_logger = "0.11.3"
|
||||
fjall = "0.6.5"
|
||||
free-icons = "0.7.0"
|
||||
minijinja = { version = "1.0.14", features = ["loader", "json", "builtins"] }
|
||||
minijinja-autoreload = "1.0.14"
|
||||
model_derive = { path = "./model_derive" }
|
||||
rand = "0.8.5"
|
||||
redb = "2.1.0"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
DROP TABLE IF EXISTS project_memberships;
|
||||
DROP TABLE IF EXISTS document_memberships;
|
||||
|
|
|
@ -9,14 +9,3 @@ CREATE TABLE IF NOT EXISTS project_memberships(
|
|||
created TIMESTAMP NOT NULL,
|
||||
updated TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS document_memberships(
|
||||
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||
|
||||
user_id UUID_TEXT NOT NULL,
|
||||
document_id UUID_TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
|
||||
created TIMESTAMP NOT NULL,
|
||||
updated TIMESTAMP NOT NULL
|
||||
);
|
||||
|
|
46
model_derive/Cargo.lock
generated
46
model_derive/Cargo.lock
generated
|
@ -1,46 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "model_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "model_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.36"
|
||||
syn = { version = "2.0.61", features = ["full", "derive"] }
|
|
@ -1,48 +0,0 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, DeriveInput, LitInt};
|
||||
|
||||
#[proc_macro_derive(Model, attributes(model_version))]
|
||||
pub fn model(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let name = input.ident;
|
||||
let attrs = input.attrs;
|
||||
|
||||
let model_version = attrs.iter().find_map(|attr| {
|
||||
if attr.path().is_ident("model_version") {
|
||||
attr.parse_args::<LitInt>().ok().and_then(|lit| {
|
||||
Some(lit.base10_parse::<u64>().unwrap())
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).unwrap_or(0);
|
||||
|
||||
let lower_name = name.to_string().to_lowercase();
|
||||
let lower_name_ident = format_ident!("{}", lower_name);
|
||||
|
||||
|
||||
let expanded = quote! {
|
||||
impl Model for #name {
|
||||
type Id = uuid::Uuid;
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn key(id: Self::Id) -> Vec<u8> {
|
||||
let mut key = vec![];
|
||||
key.extend_from_slice(format!("{}:{}:", #lower_name, #model_version).as_bytes());
|
||||
key.extend_from_slice(&id.into_bytes());
|
||||
key
|
||||
}
|
||||
|
||||
fn partition(kv_handle: &KvHandle) -> &PartitionHandle {
|
||||
&kv_handle.#lower_name_ident
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
|
@ -60,7 +60,7 @@ pub enum AdminCommand {
|
|||
}
|
||||
|
||||
async fn handle_create_user(db_url: &str, new_user: NewUser) -> Result<()> {
|
||||
let mut db = establish_connection(&db_url);
|
||||
let mut db = establish_connection(db_url);
|
||||
|
||||
let user = users::Query::new(&mut db).create(new_user)?;
|
||||
println!("User created successfully with id = {}", user.id);
|
||||
|
@ -69,7 +69,7 @@ async fn handle_create_user(db_url: &str, new_user: NewUser) -> Result<()> {
|
|||
}
|
||||
|
||||
async fn handle_list_users(db_url: &str) -> Result<()> {
|
||||
let mut db = establish_connection(&db_url);
|
||||
let mut db = establish_connection(db_url);
|
||||
|
||||
let users = users::Query::new(&mut db).all()?;
|
||||
|
||||
|
|
|
@ -6,27 +6,20 @@ use std::sync::Arc;
|
|||
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
|
||||
use crate::{handler::internal_server_error, kv::KvHandle, prelude::*};
|
||||
use crate::{handler::internal_server_error, prelude::*};
|
||||
|
||||
pub type ConnectionPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
pub db_pool: ConnectionPool,
|
||||
// TODO: add a design doc explaining why this not relational
|
||||
pub kv_handles: KvHandle,
|
||||
template_loader: Arc<AutoReloader>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(
|
||||
db: ConnectionPool,
|
||||
kv_handles: KvHandle,
|
||||
template_loader: AutoReloader,
|
||||
) -> Context {
|
||||
pub fn new(db: ConnectionPool, template_loader: AutoReloader) -> Context {
|
||||
Context {
|
||||
db_pool: db,
|
||||
kv_handles,
|
||||
template_loader: Arc::new(template_loader),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ use diesel::r2d2::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.
|
||||
|
@ -44,10 +46,6 @@ pub fn migrate(conn: &mut SqliteConnection) {
|
|||
conn.run_pending_migrations(MIGRATIONS).unwrap();
|
||||
}
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{password, prelude::*};
|
||||
|
||||
pub struct NewUser {
|
||||
pub full_name: String,
|
||||
pub email: String,
|
||||
|
|
|
@ -1,48 +1,59 @@
|
|||
use axum::{extract::Path, response::Redirect, Form};
|
||||
use axum::{extract::Path, http::StatusCode, response::Redirect, Form};
|
||||
use axum_login::AuthSession;
|
||||
|
||||
use crate::{handler::internal_server_error, models::users::User, prelude::*};
|
||||
use crate::{
|
||||
handler::internal_error,
|
||||
models::{
|
||||
documents::{self, NewDocument},
|
||||
users::User,
|
||||
},
|
||||
permissions::{self, query::Permission},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
pub async fn documents_page(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
) -> Response {
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
if let Some(user) = auth_session.user {
|
||||
render_documents_page(ctx, user).await
|
||||
} else {
|
||||
Redirect::to("/login").into_response()
|
||||
Ok(Redirect::to("/login").into_response())
|
||||
}
|
||||
}
|
||||
|
||||
async fn render_documents_page(ctx: Context, user: User) -> Response {
|
||||
todo!()
|
||||
//let documents = ModelPermission::user_documents(&ctx.kv_handles, user.id).unwrap_or_default();
|
||||
async fn render_documents_page(ctx: Context, user: User) -> Result<Response, (StatusCode, String)> {
|
||||
let mut db = ctx.db_pool.get().map_err(internal_error)?;
|
||||
let documents =
|
||||
permissions::query::accessible_documents(&mut db, &user.id).map_err(internal_error)?;
|
||||
|
||||
//let values = context! {
|
||||
// user => user,
|
||||
// documents => documents,
|
||||
//};
|
||||
let values = context! {
|
||||
user => user,
|
||||
documents => documents,
|
||||
};
|
||||
|
||||
//ctx.render_resp("documents/list_documents.html", values)
|
||||
Ok(ctx.render_resp("documents/list_documents.html", values))
|
||||
}
|
||||
|
||||
pub async fn create_document_page(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
) -> Response {
|
||||
todo!()
|
||||
//let user = match auth_session.user {
|
||||
// Some(user) => user,
|
||||
// None => return Redirect::to("/login").into_response(),
|
||||
//};
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/login").into_response()),
|
||||
};
|
||||
|
||||
//let projects = ModelPermission::user_projects(&ctx.kv_handles, user.id).unwrap_or_default();
|
||||
let mut db = ctx.db_pool.get().map_err(internal_error)?;
|
||||
|
||||
//let values = context! {
|
||||
// user => user,
|
||||
// projects => projects,
|
||||
//};
|
||||
//ctx.render_resp("documents/create_document.html", values)
|
||||
let projects =
|
||||
permissions::query::accessible_projects(&mut db, &user.id).map_err(internal_error)?;
|
||||
|
||||
let values = context! {
|
||||
user => user,
|
||||
projects => projects,
|
||||
};
|
||||
Ok(ctx.render_resp("documents/create_document.html", values))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -55,77 +66,69 @@ pub async fn create_document_submit(
|
|||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
form: Form<CreateDocumentSubmission>,
|
||||
) -> Response {
|
||||
todo!()
|
||||
//let user = match auth_session.user {
|
||||
// Some(user) => user,
|
||||
// None => return Redirect::to("/login").into_response(),
|
||||
//};
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/login").into_response()),
|
||||
};
|
||||
let mut db = ctx.db_pool.get().map_err(internal_error)?;
|
||||
|
||||
//let project = match ModelPermission::user_project(&ctx.kv_handles, user.id, form.project_id) {
|
||||
// Ok(Some(project)) => project,
|
||||
// Ok(None) => return Redirect::to("/documents/create").into_response(),
|
||||
// Err(err) => {
|
||||
// error!(?err, "failed to access kv store");
|
||||
// return Redirect::to("/documents/create").into_response();
|
||||
// }
|
||||
//};
|
||||
let project_allowed = permissions::query::check_user_project(
|
||||
&mut db,
|
||||
&user.id,
|
||||
&form.project_id.to_string(),
|
||||
Permission::Write,
|
||||
)
|
||||
.map_err(internal_error)?;
|
||||
|
||||
//let document = Document {
|
||||
// id: Uuid::now_v7(),
|
||||
// project_id: project.id,
|
||||
// title: form.title.to_owned(),
|
||||
// content: "".to_owned(),
|
||||
//};
|
||||
if !project_allowed {
|
||||
return Err((StatusCode::FORBIDDEN, "permission denied".to_owned()));
|
||||
}
|
||||
|
||||
//if let Err(err) = document.save(&ctx.kv_handles) {
|
||||
// error!(?err, "failed to save document");
|
||||
// return internal_server_error();
|
||||
//}
|
||||
//info!(?document, "document created");
|
||||
let new_document = NewDocument::new(
|
||||
&user.id,
|
||||
&form.project_id.to_string(),
|
||||
form.title.to_owned(),
|
||||
"".to_owned(),
|
||||
);
|
||||
|
||||
//let permission = ModelPermission {
|
||||
// user_id: user.id,
|
||||
// model_type: ModelType::Document,
|
||||
// role: Permission::Admin,
|
||||
// model_id: document.id,
|
||||
//};
|
||||
let document = documents::query::create(&mut db, new_document).map_err(internal_error)?;
|
||||
info!(?document, "document created");
|
||||
|
||||
//if let Err(err) = permission.add(&ctx.kv_handles) {
|
||||
// error!(?err, "failed to save new project permission");
|
||||
// return internal_server_error();
|
||||
//}
|
||||
|
||||
//Redirect::to("/documents").into_response()
|
||||
Ok(Redirect::to("/documents").into_response())
|
||||
}
|
||||
|
||||
pub async fn edit_document_page(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
Path((id,)): Path<(Uuid,)>,
|
||||
) -> Response {
|
||||
todo!()
|
||||
//let user = match auth_session.user {
|
||||
// Some(user) => user,
|
||||
// None => return Redirect::to("/login").into_response(),
|
||||
//};
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/login").into_response()),
|
||||
};
|
||||
|
||||
//let document = match ModelPermission::user_document(&ctx.kv_handles, user.id, id) {
|
||||
// Ok(Some(document)) => document,
|
||||
// Ok(None) => return Redirect::to("/documents").into_response(),
|
||||
// Err(err) => {
|
||||
// error!(?err, "failed to load document");
|
||||
// return internal_server_error();
|
||||
// }
|
||||
//};
|
||||
let mut db = ctx.db_pool.get().map_err(internal_error)?;
|
||||
|
||||
//dbg!(&document);
|
||||
//let values = context! {
|
||||
// user => user,
|
||||
// document => document,
|
||||
//};
|
||||
let document_allowed = permissions::query::check_user_document(
|
||||
&mut db,
|
||||
&user.id,
|
||||
&id.to_string(),
|
||||
Permission::Write,
|
||||
).map_err(internal_error)?;
|
||||
|
||||
//ctx.render_resp("documents/edit_document.html", values)
|
||||
if !document_allowed {
|
||||
return Err((StatusCode::FORBIDDEN, "permission denied".to_owned()));
|
||||
}
|
||||
|
||||
let document = documents::query::by_id(&mut db, &id.to_string()).map_err(internal_error)?;
|
||||
|
||||
let values = context! {
|
||||
user => user,
|
||||
document => document,
|
||||
};
|
||||
|
||||
Ok(ctx.render_resp("documents/edit_document.html", values))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -139,34 +142,31 @@ pub async fn edit_document_submit(
|
|||
auth_session: AuthSession<Context>,
|
||||
Path((document_id,)): Path<(Uuid,)>,
|
||||
form: Form<EditDocumentSubmission>,
|
||||
) -> Response {
|
||||
todo!()
|
||||
//let user = match auth_session.user {
|
||||
// Some(user) => user,
|
||||
// None => return Redirect::to("/login").into_response(),
|
||||
//};
|
||||
) -> Result<Response, (StatusCode, String)> {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/login").into_response()),
|
||||
};
|
||||
|
||||
//let mut document = match ModelPermission::user_document(&ctx.kv_handles, user.id, document_id) {
|
||||
// Ok(Some(document)) => document,
|
||||
// Ok(None) => return Redirect::to("/documents").into_response(),
|
||||
// Err(err) => {
|
||||
// error!(?err, "failed to load document");
|
||||
// return internal_server_error();
|
||||
// }
|
||||
//};
|
||||
let mut db = ctx.db_pool.get().map_err(internal_error)?;
|
||||
|
||||
//let new_document = Document {
|
||||
// id: document.id,
|
||||
// project_id: document.id,
|
||||
// title: form.title.to_owned(),
|
||||
// content: form.content.to_owned(),
|
||||
//};
|
||||
let document_allowed = permissions::query::check_user_document(
|
||||
&mut db,
|
||||
&user.id,
|
||||
&document_id.to_string(),
|
||||
Permission::Write,
|
||||
).map_err(internal_error)?;
|
||||
|
||||
//if let Err(err) = new_document.save(&ctx.kv_handles) {
|
||||
// error!(?err, "failed to save document");
|
||||
// return internal_server_error();
|
||||
//}
|
||||
//info!(?new_document, "document updated");
|
||||
|
||||
//Redirect::to("/documents").into_response()
|
||||
if !document_allowed {
|
||||
return Err((StatusCode::FORBIDDEN, "permission denied".to_owned()));
|
||||
}
|
||||
|
||||
documents::query::update(
|
||||
&mut db,
|
||||
&document_id.to_string(),
|
||||
form.title.to_owned(),
|
||||
form.content.to_owned(),
|
||||
).map_err(internal_error)?;
|
||||
|
||||
Ok(Redirect::to("/documents").into_response())
|
||||
}
|
||||
|
|
42
src/kv.rs
42
src/kv.rs
|
@ -1,42 +0,0 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use fjall::{Config, Keyspace, PartitionCreateOptions, PartitionHandle};
|
||||
|
||||
/// Contains the handles needed to reference key-value data.
|
||||
///
|
||||
/// This contains both the Keyspace and multiple PartitionHandle.
|
||||
/// The Keyspace allows operational control and reporting at the top level,
|
||||
/// while each PartitionHandle controls reading, writing, and removing from a
|
||||
/// particular partition of the data.
|
||||
///
|
||||
/// All fields are public, because this is meant to be used internally as a
|
||||
/// wrapper to pass everything around, instead of passing each handle around by
|
||||
/// itself.
|
||||
#[derive(Clone)]
|
||||
pub struct KvHandle {
|
||||
pub keyspace: Keyspace,
|
||||
|
||||
pub project: PartitionHandle,
|
||||
pub document: PartitionHandle,
|
||||
pub permissions: PartitionHandle,
|
||||
}
|
||||
|
||||
impl KvHandle {
|
||||
pub fn open<P: AsRef<Path>>(p: P) -> Result<KvHandle> {
|
||||
// TODO: those should probably be configurable, or like, not just hard coded.
|
||||
let config = Config::new(p).flush_workers(4).compaction_workers(4);
|
||||
let keyspace = Keyspace::open(config)?;
|
||||
|
||||
let project = keyspace.open_partition("project", PartitionCreateOptions::default())?;
|
||||
let document = keyspace.open_partition("document", PartitionCreateOptions::default())?;
|
||||
let permissions = keyspace.open_partition("permissions", PartitionCreateOptions::default())?;
|
||||
|
||||
Ok(KvHandle {
|
||||
keyspace,
|
||||
project,
|
||||
document,
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,11 +5,10 @@ pub mod handler;
|
|||
pub mod logging;
|
||||
pub mod models;
|
||||
pub mod password;
|
||||
pub mod permissions;
|
||||
pub mod prelude;
|
||||
pub mod schema;
|
||||
pub mod serialize;
|
||||
pub mod server;
|
||||
pub mod session;
|
||||
pub mod templates;
|
||||
pub mod kv;
|
||||
pub mod permissions;
|
||||
|
|
|
@ -3,7 +3,6 @@ use thiserror::Error;
|
|||
pub mod users;
|
||||
pub mod projects;
|
||||
pub mod types;
|
||||
pub mod document_memberships;
|
||||
pub mod project_memberships;
|
||||
pub mod documents;
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use diesel::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::schema::documents::dsl;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::DbError;
|
||||
|
||||
#[derive(Queryable, Selectable, Debug, Clone)]
|
||||
#[derive(Queryable, Selectable, Debug, Clone, Serialize)]
|
||||
#[diesel(table_name = crate::schema::documents)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Document {
|
||||
|
@ -14,7 +15,9 @@ pub struct Document {
|
|||
pub project_id: String,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
#[serde(skip)]
|
||||
pub created: chrono::NaiveDateTime,
|
||||
#[serde(skip)]
|
||||
pub updated: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
|
@ -29,7 +32,7 @@ pub struct NewDocument {
|
|||
}
|
||||
|
||||
impl NewDocument {
|
||||
pub fn new(creator_id: Uuid, project_id: Uuid, title: String, content: String) -> Self {
|
||||
pub fn new(creator_id: &str, project_id: &str, title: String, content: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
creator_id: creator_id.to_string(),
|
||||
|
@ -40,19 +43,39 @@ impl NewDocument {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Query<'a> {
|
||||
db: &'a mut SqliteConnection,
|
||||
pub mod query {
|
||||
use super::*;
|
||||
|
||||
pub fn create(db: &mut SqliteConnection, new_document: NewDocument) -> Result<Document, DbError> {
|
||||
diesel::insert_into(dsl::documents)
|
||||
.values(&new_document)
|
||||
.execute(db)?;
|
||||
|
||||
let document = dsl::documents
|
||||
.filter(dsl::id.eq(&new_document.id))
|
||||
.first(db)?;
|
||||
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
impl<'a> Query<'a> {
|
||||
pub fn new(db: &'a mut SqliteConnection) -> Self {
|
||||
Self { db }
|
||||
pub fn update(db: &mut SqliteConnection, document_id: &str, title: String, content: String) -> Result<Document, DbError> {
|
||||
diesel::update(dsl::documents.filter(dsl::id.eq(document_id)))
|
||||
.set((dsl::title.eq(title), dsl::content.eq(content)))
|
||||
.execute(db)?;
|
||||
|
||||
let document = dsl::documents
|
||||
.filter(dsl::id.eq(document_id))
|
||||
.first(db)?;
|
||||
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
pub fn for_user(&mut self, user_id: String) -> Result<Vec<Document>, DbError> {
|
||||
let documents = dsl::documents
|
||||
.filter(dsl::creator_id.eq(user_id.to_string()))
|
||||
.load::<Document>(self.db)?;
|
||||
Ok(documents)
|
||||
pub fn by_id(db: &mut SqliteConnection, document_id: &str) -> Result<Option<Document>, DbError> {
|
||||
let document = dsl::documents
|
||||
.filter(dsl::id.eq(document_id))
|
||||
.first::<Document>(db)
|
||||
.optional()?;
|
||||
|
||||
Ok(document)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,64 @@
|
|||
pub mod query {
|
||||
use diesel::prelude::*;
|
||||
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 {
|
||||
Read,
|
||||
Write,
|
||||
Admin,
|
||||
}
|
||||
|
||||
pub fn check_user_project(
|
||||
db: &mut SqliteConnection,
|
||||
user_id: &str,
|
||||
project_id: &str,
|
||||
permission: Permission,
|
||||
) -> Result<bool, diesel::result::Error> {
|
||||
use crate::schema::project_memberships::dsl as pm;
|
||||
|
||||
if permission == Permission::Admin {
|
||||
let is_admin = pm::project_memberships
|
||||
.filter(pm::user_id.eq(user_id))
|
||||
.filter(pm::project_id.eq(project_id))
|
||||
.filter(pm::role.eq(ProjectRole::Admin.to_string()))
|
||||
.count()
|
||||
.get_result::<i64>(db)?;
|
||||
|
||||
Ok(is_admin > 0)
|
||||
} else {
|
||||
let is_member = pm::project_memberships
|
||||
.filter(pm::user_id.eq(user_id))
|
||||
.filter(pm::project_id.eq(project_id))
|
||||
.count()
|
||||
.get_result::<i64>(db)?;
|
||||
|
||||
Ok(is_member > 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_user_document(
|
||||
db: &mut SqliteConnection,
|
||||
user_id: &str,
|
||||
document_id: &str,
|
||||
permission: Permission,
|
||||
) -> Result<bool, diesel::result::Error> {
|
||||
use crate::schema::documents::dsl as d;
|
||||
|
||||
let document = d::documents
|
||||
.filter(d::id.eq(document_id))
|
||||
.first::<Document>(db)
|
||||
.optional()?;
|
||||
|
||||
match document {
|
||||
Some(doc) => check_user_project(db, user_id, &doc.project_id, permission),
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Users have permissions directly on projects which they are members of.
|
||||
pub fn accessible_project_ids(
|
||||
db: &mut SqliteConnection,
|
||||
|
@ -57,7 +112,7 @@ pub mod query {
|
|||
|
||||
let document_ids = direct_documents
|
||||
.into_iter()
|
||||
.chain(project_documents.into_iter())
|
||||
.chain(project_documents)
|
||||
.collect();
|
||||
|
||||
Ok(document_ids)
|
||||
|
|
|
@ -1,16 +1,5 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
document_memberships (id) {
|
||||
id -> Integer,
|
||||
user_id -> Text,
|
||||
document_id -> Text,
|
||||
role -> Text,
|
||||
created -> Timestamp,
|
||||
updated -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
documents (id) {
|
||||
id -> Text,
|
||||
|
@ -59,7 +48,6 @@ diesel::table! {
|
|||
}
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
document_memberships,
|
||||
documents,
|
||||
project_memberships,
|
||||
projects,
|
||||
|
|
|
@ -1,16 +1,38 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{routing::{get, post}, Router};
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
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,
|
||||
trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer},
|
||||
};
|
||||
use tower_sessions::SessionManagerLayer;
|
||||
use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::{config::CommandLineOptions, context::Context, 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}}, kv::KvHandle, logging::setup_logging, templates::make_template_loader};
|
||||
use crate::{
|
||||
config::CommandLineOptions,
|
||||
context::Context,
|
||||
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,
|
||||
};
|
||||
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/");
|
||||
|
||||
|
@ -30,10 +52,7 @@ pub async fn run() -> Result<()> {
|
|||
|
||||
let session_layer = create_session_manager_layer().await?;
|
||||
|
||||
// TODO: better name, also make it an option
|
||||
let kv_handles = KvHandle::open("./kvdata/")?;
|
||||
|
||||
let context = Context::new(db_pool, kv_handles, template_loader);
|
||||
let context = Context::new(db_pool, template_loader);
|
||||
|
||||
let auth_backend = context.clone();
|
||||
let auth_layer = AuthManagerLayerBuilder::new(auth_backend, session_layer.clone()).build();
|
||||
|
|
|
@ -47,7 +47,7 @@ impl AuthnBackend for Context {
|
|||
let mut db = self.db_pool.get()?;
|
||||
let mut q = users::Query::new(&mut db);
|
||||
|
||||
let user = q.by_id(&user_id)?;
|
||||
let user = q.by_id(user_id)?;
|
||||
|
||||
Ok(Some(user))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue