multi tenancy part 1
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::models::{Document, Folder, NewFolder};
|
||||
use crate::schema::{documents, folders};
|
||||
use crate::state::AppState;
|
||||
use crate::{
|
||||
auth::AuthenticatedUser,
|
||||
auth::TenantScopedConn,
|
||||
error::{AppError, AppResult},
|
||||
};
|
||||
|
||||
@@ -70,15 +70,17 @@ pub struct FolderInfo {
|
||||
}
|
||||
|
||||
pub async fn ensure_folder_path(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
Json(payload): Json<EnsureFolderPathRequest>,
|
||||
) -> AppResult<Json<FolderResponse>> {
|
||||
if payload.segments.is_empty() {
|
||||
return Err(AppError::bad_request("segments must not be empty"));
|
||||
}
|
||||
|
||||
let mut conn = state.db()?;
|
||||
|
||||
let target_folder = conn.transaction::<Folder, AppError, _>(|conn| {
|
||||
let mut current_parent = payload.parent_id;
|
||||
let mut last_folder: Option<Folder> = None;
|
||||
@@ -93,12 +95,14 @@ pub async fn ensure_folder_path(
|
||||
folders::table
|
||||
.filter(folders::parent_id.eq(Some(parent_id)))
|
||||
.filter(folders::name.eq(name))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first(conn)
|
||||
.optional()?
|
||||
} else {
|
||||
folders::table
|
||||
.filter(folders::parent_id.is_null())
|
||||
.filter(folders::name.eq(name))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first(conn)
|
||||
.optional()?
|
||||
};
|
||||
@@ -110,6 +114,7 @@ pub async fn ensure_folder_path(
|
||||
id: Uuid::new_v4(),
|
||||
name: name.to_string(),
|
||||
parent_id: current_parent,
|
||||
tenant_id,
|
||||
};
|
||||
|
||||
diesel::insert_into(folders::table)
|
||||
@@ -132,19 +137,22 @@ pub async fn ensure_folder_path(
|
||||
}
|
||||
|
||||
pub async fn create_folder(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
Json(payload): Json<CreateFolderRequest>,
|
||||
) -> AppResult<Json<FolderResponse>> {
|
||||
if payload.name.trim().is_empty() {
|
||||
return Err(AppError::bad_request("name must not be empty"));
|
||||
}
|
||||
|
||||
let mut conn = state.db()?;
|
||||
|
||||
let new_folder = NewFolder {
|
||||
id: Uuid::new_v4(),
|
||||
name: payload.name.trim().to_string(),
|
||||
parent_id: payload.parent_id,
|
||||
tenant_id,
|
||||
};
|
||||
|
||||
diesel::insert_into(folders::table)
|
||||
@@ -161,10 +169,13 @@ pub async fn list_folder_contents(
|
||||
State(state): State<AppState>,
|
||||
Path(folder_identifier): Path<String>,
|
||||
Query(query): Query<FolderContentsQuery>,
|
||||
user: AuthenticatedUser,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
user_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
) -> AppResult<Json<FolderContentsResponse>> {
|
||||
let mut conn = state.db()?;
|
||||
|
||||
let folder_id = if folder_identifier.eq_ignore_ascii_case("root") {
|
||||
None
|
||||
} else {
|
||||
@@ -176,7 +187,10 @@ pub async fn list_folder_contents(
|
||||
|
||||
let folder = match folder_id {
|
||||
Some(id) => Some(folder_to_info(
|
||||
folders::table.find(id).first::<Folder>(&mut conn)?,
|
||||
folders::table
|
||||
.find(id)
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first::<Folder>(&mut conn)?,
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
@@ -184,11 +198,13 @@ pub async fn list_folder_contents(
|
||||
let child_folders: Vec<Folder> = if let Some(parent_id) = folder_id {
|
||||
folders::table
|
||||
.filter(folders::parent_id.eq(parent_id))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.order(folders::name.asc())
|
||||
.load(&mut conn)?
|
||||
} else {
|
||||
folders::table
|
||||
.filter(folders::parent_id.is_null())
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.order(folders::name.asc())
|
||||
.load(&mut conn)?
|
||||
};
|
||||
@@ -197,6 +213,7 @@ pub async fn list_folder_contents(
|
||||
let documents = if query.include_documents {
|
||||
let docs_query = documents::table
|
||||
.filter(documents::deleted_at.is_null())
|
||||
.filter(documents::tenant_id.eq(tenant_id))
|
||||
.order(documents::uploaded_at.desc());
|
||||
|
||||
let docs: Vec<Document> = if let Some(current_folder) = folder_id {
|
||||
@@ -214,7 +231,7 @@ pub async fn list_folder_contents(
|
||||
let mut correspondents_map = load_correspondents_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_versions = load_primary_assets(&state, &docs).await?;
|
||||
let primary_versions = load_primary_assets(&state, tenant_id, &docs).await?;
|
||||
|
||||
let mut documents = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
@@ -223,7 +240,7 @@ pub async fn list_folder_contents(
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
documents.push(to_document_response(
|
||||
&state,
|
||||
user.user_id,
|
||||
user_id,
|
||||
doc,
|
||||
tags,
|
||||
correspondents,
|
||||
@@ -244,16 +261,23 @@ pub async fn list_folder_contents(
|
||||
}
|
||||
|
||||
pub async fn delete_folder(
|
||||
State(state): State<AppState>,
|
||||
Path(folder_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
) -> AppResult<StatusCode> {
|
||||
let mut conn = state.db()?;
|
||||
|
||||
conn.transaction::<_, AppError, _>(|conn| {
|
||||
folders::table.find(folder_id).first::<Folder>(conn)?;
|
||||
folders::table
|
||||
.find(folder_id)
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first::<Folder>(conn)?;
|
||||
|
||||
let has_child_folders: bool = diesel::select(exists(
|
||||
folders::table.filter(folders::parent_id.eq(Some(folder_id))),
|
||||
folders::table
|
||||
.filter(folders::parent_id.eq(Some(folder_id)))
|
||||
.filter(folders::tenant_id.eq(tenant_id)),
|
||||
))
|
||||
.get_result(conn)?;
|
||||
|
||||
@@ -266,6 +290,7 @@ pub async fn delete_folder(
|
||||
let has_documents: bool = diesel::select(exists(
|
||||
documents::table
|
||||
.filter(documents::folder_id.eq(Some(folder_id)))
|
||||
.filter(documents::tenant_id.eq(tenant_id))
|
||||
.filter(documents::deleted_at.is_null()),
|
||||
))
|
||||
.get_result(conn)?;
|
||||
@@ -276,7 +301,12 @@ pub async fn delete_folder(
|
||||
));
|
||||
}
|
||||
|
||||
diesel::delete(folders::table.find(folder_id)).execute(conn)?;
|
||||
diesel::delete(
|
||||
folders::table
|
||||
.filter(folders::id.eq(folder_id))
|
||||
.filter(folders::tenant_id.eq(tenant_id)),
|
||||
)
|
||||
.execute(conn)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -285,14 +315,19 @@ pub async fn delete_folder(
|
||||
}
|
||||
|
||||
pub async fn update_folder(
|
||||
State(state): State<AppState>,
|
||||
Path(folder_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
Json(payload): Json<UpdateFolderRequest>,
|
||||
) -> AppResult<StatusCode> {
|
||||
let mut conn = state.db()?;
|
||||
|
||||
conn.transaction::<(), AppError, _>(|conn| {
|
||||
let folder: Folder = folders::table.find(folder_id).first(conn)?;
|
||||
let folder: Folder = folders::table
|
||||
.find(folder_id)
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first(conn)?;
|
||||
|
||||
let mut next_parent = folder.parent_id;
|
||||
let mut parent_changed = false;
|
||||
@@ -303,9 +338,12 @@ pub async fn update_folder(
|
||||
}
|
||||
|
||||
if let Some(parent_id) = parent_request {
|
||||
let _parent: Folder = folders::table.find(parent_id).first(conn)?;
|
||||
let _parent: Folder = folders::table
|
||||
.find(parent_id)
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first(conn)?;
|
||||
|
||||
let descendant_ids = gather_descendant_folder_ids(conn, folder_id)?;
|
||||
let descendant_ids = gather_descendant_folder_ids(conn, tenant_id, folder_id)?;
|
||||
if descendant_ids.contains(&parent_id) {
|
||||
return Err(AppError::bad_request(
|
||||
"cannot move folder into itself or a descendant",
|
||||
@@ -341,6 +379,7 @@ pub async fn update_folder(
|
||||
.filter(folders::parent_id.eq(Some(parent_id)))
|
||||
.filter(folders::name.eq(&new_name))
|
||||
.filter(folders::id.ne(folder_id))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first::<Folder>(conn)
|
||||
.optional()?
|
||||
} else {
|
||||
@@ -348,6 +387,7 @@ pub async fn update_folder(
|
||||
.filter(folders::parent_id.is_null())
|
||||
.filter(folders::name.eq(&new_name))
|
||||
.filter(folders::id.ne(folder_id))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.first::<Folder>(conn)
|
||||
.optional()?
|
||||
};
|
||||
@@ -358,12 +398,16 @@ pub async fn update_folder(
|
||||
));
|
||||
}
|
||||
|
||||
diesel::update(folders::table.find(folder_id))
|
||||
.set((
|
||||
folders::parent_id.eq(next_parent),
|
||||
folders::name.eq(&new_name),
|
||||
))
|
||||
.execute(conn)?;
|
||||
diesel::update(
|
||||
folders::table
|
||||
.find(folder_id)
|
||||
.filter(folders::tenant_id.eq(tenant_id)),
|
||||
)
|
||||
.set((
|
||||
folders::parent_id.eq(next_parent),
|
||||
folders::name.eq(&new_name),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -383,6 +427,7 @@ fn folder_to_info(folder: Folder) -> FolderInfo {
|
||||
|
||||
pub(super) fn gather_descendant_folder_ids(
|
||||
conn: &mut PgConnection,
|
||||
tenant_id: Uuid,
|
||||
folder_id: Uuid,
|
||||
) -> AppResult<Vec<Uuid>> {
|
||||
let mut ids = vec![folder_id];
|
||||
@@ -391,6 +436,7 @@ pub(super) fn gather_descendant_folder_ids(
|
||||
while let Some(current) = queue.pop() {
|
||||
let child_ids: Vec<Uuid> = folders::table
|
||||
.filter(folders::parent_id.eq(Some(current)))
|
||||
.filter(folders::tenant_id.eq(tenant_id))
|
||||
.select(folders::id)
|
||||
.load(conn)?;
|
||||
queue.extend(child_ids.iter().copied());
|
||||
|
||||
Reference in New Issue
Block a user