feat(drive): remove create_home_folder
remove create_home_folder() & ensure_home_folder()
now: on_user_created() and on_user_login both() call provision_if_needed()
which calls **create_personal_drive_atomic()**
add a helper to find Personal drive for a user and also it's root directorry
This commit is contained in:
@@ -96,16 +96,6 @@ pub trait FolderUseCase: Send + Sync + 'static {
|
||||
/// Deletes a folder (ownership verified against caller_id)
|
||||
async fn delete_folder_with_perms(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError>;
|
||||
|
||||
/// Creates a root-level home folder for a user during registration.
|
||||
/// `drive_id` is the user's personal drive; the wrapper folder stays
|
||||
/// during the D0 dual-write window (retires in M2b later).
|
||||
async fn create_home_folder(
|
||||
&self,
|
||||
user_id: Uuid,
|
||||
drive_id: Uuid,
|
||||
name: String,
|
||||
) -> Result<FolderDto, DomainError>;
|
||||
|
||||
/// Lists every folder in a subtree rooted at `folder_id` (inclusive),
|
||||
/// ordered by path. Uses ltree `<@` — single GiST-indexed query.
|
||||
///
|
||||
|
||||
@@ -127,7 +127,7 @@ pub enum LogoutReason {
|
||||
|
||||
/// How aggressively `on_user_deleted` cleanup should run. Today both
|
||||
/// variants are equivalent (only `AuditLifecycleHook` exists, and it logs
|
||||
/// regardless). The split exists so PR 4's `HomeFolderLifecycleHook` can
|
||||
/// regardless). The split exists so PR 4's `PersonalDriveLifecycleHook` can
|
||||
/// trash on `AdminDelete` but hard-delete on `GdprPurge`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DeletionMode {
|
||||
|
||||
@@ -124,7 +124,7 @@ pub struct AuthApplicationService {
|
||||
token_service: Arc<JwtTokenService>,
|
||||
/// Dispatcher for user-lifecycle events. `None` only in tests that don't
|
||||
/// exercise the lifecycle path; production DI always wires this.
|
||||
/// HomeFolderLifecycleHook (registered on this dispatcher) owns the
|
||||
/// PersonalDriveLifecycleHook (registered on this dispatcher) owns the
|
||||
/// per-user folder provisioning that AuthApplicationService used to do
|
||||
/// inline pre-PR 3.
|
||||
user_lifecycle: Option<Arc<UserLifecycleService>>,
|
||||
@@ -404,7 +404,7 @@ impl AuthApplicationService {
|
||||
// Save user
|
||||
let created_user = self.user_storage.create_user(user).await?;
|
||||
|
||||
// Lifecycle: HomeFolderLifecycleHook handles personal-folder
|
||||
// Lifecycle: PersonalDriveLifecycleHook handles personal-folder
|
||||
// creation (was inlined here pre-PR 3); audit log + future
|
||||
// provisioning steps land here too.
|
||||
if let Some(lc) = &self.user_lifecycle {
|
||||
@@ -506,8 +506,8 @@ impl AuthApplicationService {
|
||||
let created_user = self.user_storage.create_user(user).await?;
|
||||
|
||||
// Lifecycle: notify hooks. PR 3 moves home-folder creation into
|
||||
// HomeFolderLifecycleHook fired here.
|
||||
// Lifecycle: HomeFolderLifecycleHook provisions the admin's
|
||||
// PersonalDriveLifecycleHook fired here.
|
||||
// Lifecycle: PersonalDriveLifecycleHook provisions the admin's
|
||||
// home folder. Audit logs the creation event.
|
||||
if let Some(lc) = &self.user_lifecycle {
|
||||
lc.dispatch_created(&created_user).await;
|
||||
@@ -654,7 +654,7 @@ impl AuthApplicationService {
|
||||
/// A second redemption attempt receives `Ok(false)` and is rejected
|
||||
/// as `AccessDenied`.
|
||||
/// 3. Load the user, verify they're active.
|
||||
/// 4. Dispatch `on_user_login` (so HomeFolderLifecycleHook can
|
||||
/// 4. Dispatch `on_user_login` (so PersonalDriveLifecycleHook can
|
||||
/// safety-net any internal user whose first credential happens
|
||||
/// to be a magic link — externals short-circuit by `is_external()`).
|
||||
/// 5. Register login + persist + issue session in the same pipeline
|
||||
@@ -1722,7 +1722,7 @@ impl AuthApplicationService {
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Lifecycle: HomeFolderLifecycleHook handles the home-folder
|
||||
// Lifecycle: PersonalDriveLifecycleHook handles the home-folder
|
||||
// provisioning (idempotent + short-circuits on is_external).
|
||||
// Audit logs the creation event.
|
||||
if let Some(lc) = &self.user_lifecycle {
|
||||
@@ -1785,7 +1785,7 @@ impl AuthApplicationService {
|
||||
/// Runs the whole flow in a single transaction so the lifecycle
|
||||
/// hooks (`SessionRevocationLifecycleHook` revoking sessions with
|
||||
/// audit, `AuthzCacheLifecycleHook` invalidating the Moka cache,
|
||||
/// `HomeFolderLifecycleHook` for future trash policy, …) can do
|
||||
/// `PersonalDriveLifecycleHook` for future trash policy, …) can do
|
||||
/// their work atomically with the user DELETE. If any hook returns
|
||||
/// `Err`, the transaction rolls back and the user remains intact.
|
||||
pub async fn delete_user_admin(&self, user_id: Uuid) -> Result<(), DomainError> {
|
||||
@@ -2260,7 +2260,7 @@ impl AuthApplicationService {
|
||||
// Lifecycle: created (audit + home-folder provisioning) +
|
||||
// login (no register_login() for a fresh OIDC user means
|
||||
// `last_login_at` is naturally None → first-login detection
|
||||
// works). HomeFolderLifecycleHook creates the home folder.
|
||||
// works). PersonalDriveLifecycleHook creates the home folder.
|
||||
if let Some(lc) = &self.user_lifecycle {
|
||||
lc.dispatch_created(&created_user).await;
|
||||
lc.dispatch_login(&created_user).await;
|
||||
@@ -2362,7 +2362,7 @@ impl AuthApplicationService {
|
||||
|
||||
// `create_personal_folder` was removed in PR 3 of the
|
||||
// UserLifecycleHook migration — home-folder provisioning is now
|
||||
// owned by `HomeFolderLifecycleHook` in folder_service.rs and runs
|
||||
// owned by `PersonalDriveLifecycleHook` in folder_service.rs and runs
|
||||
// via `dispatch_created` / `dispatch_login`.
|
||||
}
|
||||
|
||||
|
||||
@@ -167,15 +167,6 @@ impl FolderService {
|
||||
) -> Result<(), DomainError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_home_folder(
|
||||
&self,
|
||||
_user_id: Uuid,
|
||||
_drive_id: Uuid,
|
||||
_name: String,
|
||||
) -> Result<FolderDto, DomainError> {
|
||||
Ok(FolderDto::empty())
|
||||
}
|
||||
}
|
||||
|
||||
FolderServiceStub
|
||||
@@ -239,30 +230,6 @@ impl FolderUseCase for FolderService {
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
/// Creates a root-level home folder for a user during registration.
|
||||
/// `drive_id` is the user's personal drive — the wrapper folder lives
|
||||
/// inside it during the D0 dual-write window (M2b retires the wrapper
|
||||
/// later).
|
||||
async fn create_home_folder(
|
||||
&self,
|
||||
user_id: Uuid,
|
||||
drive_id: Uuid,
|
||||
name: String,
|
||||
) -> Result<FolderDto, DomainError> {
|
||||
let folder = self
|
||||
.folder_storage
|
||||
.create_home_folder(user_id, drive_id, name)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error(
|
||||
"FolderStorage",
|
||||
format!("Failed to create home folder: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
async fn list_subtree_folders(&self, folder_id: &str) -> Result<Vec<FolderDto>, DomainError> {
|
||||
let folders = self.folder_storage.list_subtree_folders(folder_id).await?;
|
||||
Ok(folders.into_iter().map(FolderDto::from).collect())
|
||||
@@ -341,7 +308,7 @@ impl FolderUseCase for FolderService {
|
||||
///
|
||||
/// **Note (post PR 3):** the self-heal block that auto-created a
|
||||
/// home folder when listing returned empty has been removed.
|
||||
/// `HomeFolderLifecycleHook` (registered on `UserLifecycleService`)
|
||||
/// `PersonalDriveLifecycleHook` (registered on `UserLifecycleService`)
|
||||
/// now provisions the folder on `on_user_created` / `on_user_login`,
|
||||
/// idempotently, so the listing path no longer needs to self-heal.
|
||||
async fn list_folders_with_perms(
|
||||
@@ -626,63 +593,6 @@ impl FolderService {
|
||||
|
||||
Ok((rows, next_cursor))
|
||||
}
|
||||
|
||||
/// Idempotently provision a home folder for a user.
|
||||
///
|
||||
/// Returns `Ok(true)` if a folder was newly created, `Ok(false)` if the
|
||||
/// user already had at least one root folder.
|
||||
///
|
||||
/// **System-level operation** — bypasses authz because this runs on
|
||||
/// the user's own behalf (during creation or login provisioning) at a
|
||||
/// point where the caller may be the engine itself, not an HTTP user.
|
||||
/// Callers must be inside trusted code paths (lifecycle hooks).
|
||||
///
|
||||
/// Used by [`HomeFolderLifecycleHook`] on `on_user_created` and
|
||||
/// `on_user_login`. Replaces the old self-heal at the listing path
|
||||
/// and the four eager `create_personal_folder` calls in
|
||||
/// `AuthApplicationService` (removed in the same PR).
|
||||
pub async fn ensure_home_folder(
|
||||
&self,
|
||||
user_id: Uuid,
|
||||
drive_id: Uuid,
|
||||
username: Option<&str>,
|
||||
) -> Result<bool, DomainError> {
|
||||
let existing = self
|
||||
.folder_storage
|
||||
.list_folders_by_owner(None, user_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error(
|
||||
"FolderStorage",
|
||||
format!("ensure_home_folder: list root folders: {}", e),
|
||||
)
|
||||
})?;
|
||||
if !existing.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let folder_name = match username {
|
||||
Some(u) => format!("My Folder - {}", u),
|
||||
None => format!("My Folder - {}", user_id),
|
||||
};
|
||||
self.folder_storage
|
||||
.create_home_folder(user_id, drive_id, folder_name.clone())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error(
|
||||
"FolderStorage",
|
||||
format!("ensure_home_folder: create: {}", e),
|
||||
)
|
||||
})?;
|
||||
tracing::info!(
|
||||
target: "user_lifecycle",
|
||||
hook = "home_folder",
|
||||
user_id = %user_id,
|
||||
folder_name = %folder_name,
|
||||
"Home folder provisioned"
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the next-page cursor from the last row of the current page.
|
||||
@@ -738,7 +648,7 @@ fn build_folder_resource_cursor(
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// HomeFolderLifecycleHook
|
||||
// PersonalDriveLifecycleHook
|
||||
//
|
||||
// Owns home-folder provisioning policy. Replaces:
|
||||
// - the 4 eager `create_personal_folder` calls in AuthApplicationService
|
||||
|
||||
@@ -1034,15 +1034,6 @@ mod tests {
|
||||
async fn delete_folder_permanently(&self, _folder_id: &str) -> Result<(), DomainError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn create_home_folder(
|
||||
&self,
|
||||
_user_id: Uuid,
|
||||
_drive_id: Uuid,
|
||||
_name: String,
|
||||
) -> Result<crate::domain::entities::folder::Folder, DomainError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
struct MockShareRepository {
|
||||
|
||||
@@ -848,15 +848,6 @@ impl FolderRepository for MockFolderRepository {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_home_folder(
|
||||
&self,
|
||||
_user_id: Uuid,
|
||||
_drive_id: Uuid,
|
||||
_name: String,
|
||||
) -> std::result::Result<Folder, DomainError> {
|
||||
Ok(Folder::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(integration_tests)]
|
||||
|
||||
@@ -132,7 +132,7 @@ impl UserLifecycleService {
|
||||
//
|
||||
// Always-on observer. Emits one structured `tracing::info!(target: "audit",
|
||||
// ...)` line per event. The only hook registered in PR 1; subsequent PRs
|
||||
// add HomeFolderLifecycleHook, AuthzCacheLifecycleHook, etc., each living
|
||||
// add PersonalDriveLifecycleHook, AuthzCacheLifecycleHook, etc., each living
|
||||
// next to the service it works for.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user