multi tenancy part 1

This commit is contained in:
2025-10-22 22:52:59 +02:00
parent 8e3f09774a
commit e175d28c2c
35 changed files with 1288 additions and 314 deletions
+77 -31
View File
@@ -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());