Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod metadata;
pub mod organization;
pub mod pagination;
pub mod profile;
pub mod profile_transfer;
25 changes: 25 additions & 0 deletions application/src/dto/profile_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use kernel::interfaces::read_model::ProfileTransferRequestProjection;

#[derive(Debug)]
pub struct ProfileTransferRequestDto {
pub nanoid: String,
pub profile_nanoid: String,
pub org_account_nanoid: String,
pub status: String,
}

impl ProfileTransferRequestDto {
pub fn new(
projection: ProfileTransferRequestProjection,
profile_nanoid: String,
org_account_nanoid: String,
status: String,
) -> Self {
Self {
nanoid: projection.nanoid().as_ref().to_string(),
profile_nanoid,
org_account_nanoid,
status,
}
}
}
1 change: 1 addition & 0 deletions application/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod media;
pub mod mute;
pub mod organization;
pub mod profile;
pub mod profile_transfer;
pub mod report;
pub mod session_context;

Expand Down
3 changes: 3 additions & 0 deletions application/src/service/characterization_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,14 +744,17 @@ delegate_database_dependence! {
kernel::interfaces::read_model::DependOnAccountReadModel { AccountReadModel, account_read_model },
kernel::interfaces::read_model::DependOnAccountReportReadModel { AccountReportReadModel, account_report_read_model },
kernel::interfaces::read_model::DependOnProfileReadModel { ProfileReadModel, profile_read_model },
kernel::interfaces::read_model::DependOnProfileTransferRequestReadModel { ProfileTransferRequestReadModel, profile_transfer_request_read_model },
kernel::interfaces::read_model::DependOnMetadataReadModel { MetadataReadModel, metadata_read_model },
kernel::interfaces::event_store::DependOnAccountEventStore { AccountEventStore, account_event_store },
kernel::interfaces::event_store::DependOnAccountReportEventStore { AccountReportEventStore, account_report_event_store },
kernel::interfaces::event_store::DependOnProfileEventStore { ProfileEventStore, profile_event_store },
kernel::interfaces::event_store::DependOnProfileTransferRequestEventStore { ProfileTransferRequestEventStore, profile_transfer_request_event_store },
kernel::interfaces::event_store::DependOnMetadataEventStore { MetadataEventStore, metadata_event_store },
kernel::interfaces::repository::DependOnAccountRepository { AccountRepository, account_repository },
kernel::interfaces::repository::DependOnAccountReportRepository { AccountReportRepository, account_report_repository },
kernel::interfaces::repository::DependOnAuthAccountRepository { AuthAccountRepository, auth_account_repository },
kernel::interfaces::repository::DependOnProfileTransferRequestRepository { ProfileTransferRequestRepository, profile_transfer_request_repository },
kernel::interfaces::repository::DependOnAuthHostRepository { AuthHostRepository, auth_host_repository },
kernel::interfaces::repository::DependOnBlockRepository { BlockRepository, block_repository },
kernel::interfaces::repository::DependOnFollowRepository { FollowRepository, follow_repository },
Expand Down
27 changes: 3 additions & 24 deletions application/src/service/profile.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
use crate::dto::profile::{CreateProfileDto, ProfileDto};
use crate::service::session_context::OrganizationContext;
use error_stack::Report;
use kernel::interfaces::database::{
DatabaseConnection, DependOnTransactionManager, TransactionManager,
};
use kernel::interfaces::database::{DependOnTransactionManager, TransactionManager};
use kernel::interfaces::event::EventApplier;
use kernel::interfaces::read_model::{
DependOnProfileQuery, DependOnProfileReadModel, ProfileQuery, ProfileReadModel,
};
use kernel::interfaces::read_model::{DependOnProfileReadModel, ProfileReadModel};
use kernel::interfaces::repository::{AggregateRepository, DependOnProfileRepository};
use kernel::prelude::entity::{Nanoid, Profile, ProfileDisplayName, ProfileId, ProfileSummary};
use kernel::KernelError;
use std::future::Future;

pub trait CreateOrganizationProfileUseCase:
'static
+ Clone
+ DependOnProfileQuery
+ DependOnProfileRepository
+ DependOnProfileReadModel
+ DependOnTransactionManager
'static + Clone + DependOnProfileRepository + DependOnProfileReadModel + DependOnTransactionManager
{
fn create_organization_profile(
&self,
Expand All @@ -36,17 +27,6 @@ pub trait CreateOrganizationProfileUseCase:
summary.validate()?;
}

let mut connection = self.database_connection().connection().await?;
if self
.profile_query()
.find_by_account_id(&mut connection, &org_context.org_account_id)
.await?
.is_some()
{
return Err(Report::new(KernelError::Rejected)
.attach_printable("Organization profile already exists"));
}

let account_id = org_context.org_account_id;
let account_nanoid = org_context.org_account_nanoid;
let deps = self.clone();
Expand Down Expand Up @@ -83,7 +63,6 @@ pub trait CreateOrganizationProfileUseCase:
impl<T> CreateOrganizationProfileUseCase for T where
T: 'static
+ Clone
+ DependOnProfileQuery
+ DependOnProfileRepository
+ DependOnProfileReadModel
+ DependOnTransactionManager
Expand Down
102 changes: 102 additions & 0 deletions application/src/service/profile_transfer/cancel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use super::resolve_personal_actor_account_among;
use error_stack::Report;
use kernel::interfaces::database::{
DatabaseConnection, DependOnDatabaseConnection, TransactionManager,
};
use kernel::interfaces::event::EventApplier;
use kernel::interfaces::read_model::{
DependOnAccountQuery, DependOnProfileTransferRequestQuery,
DependOnProfileTransferRequestReadModel, ProfileTransferRequestQuery,
ProfileTransferRequestReadModel,
};
use kernel::interfaces::repository::{
AggregateRepository, DependOnProfileTransferRequestRepository,
};
use kernel::prelude::entity::{AuthAccountId, Nanoid, ProfileTransferRequest};
use kernel::KernelError;
use std::future::Future;

#[cfg(test)]
mod tests;

pub trait CancelProfileTransferRequestUseCase: 'static + Sync + Send + Clone {
fn cancel_profile_transfer_request<'a>(
&'a self,
auth_id: &'a AuthAccountId,
request_nanoid: String,
) -> impl Future<Output = error_stack::Result<(), KernelError>> + Send + 'a;
}

impl<T> CancelProfileTransferRequestUseCase for T
where
T: 'static
+ Sync
+ Send
+ Clone
+ DependOnAccountQuery
+ DependOnProfileTransferRequestQuery
+ DependOnProfileTransferRequestReadModel
+ DependOnProfileTransferRequestRepository
+ DependOnDatabaseConnection
+ kernel::interfaces::database::DependOnTransactionManager,
{
fn cancel_profile_transfer_request<'a>(
&'a self,
auth_id: &'a AuthAccountId,
request_nanoid: String,
) -> impl Future<Output = error_stack::Result<(), KernelError>> + Send + 'a {
async move {
let mut connection = self.database_connection().connection().await?;
let request_nanoid = Nanoid::<ProfileTransferRequest>::new(request_nanoid);
let projection = self
.profile_transfer_request_query()
.find_by_nanoid(&mut connection, &request_nanoid)
.await?
.ok_or_else(|| {
Report::new(KernelError::NotFound).attach_printable(format!(
"Profile transfer request not found: {}",
request_nanoid.as_ref()
))
})?;

let request_id = projection.id().clone();
let from_account_id = projection.from_account_id().clone();

resolve_personal_actor_account_among(self, auth_id, &from_account_id).await?;

let deps = self.clone();
self.transaction_manager()
.transaction(move |executor| {
Box::pin(async move {
let (request, current_version) = deps
.profile_transfer_request_repository()
.load(executor, &request_id)
.await?
.into_parts();
if !request.status().is_pending() {
return Err(Report::new(KernelError::Rejected)
.attach_printable("Profile transfer request is not pending"));
}
let event = deps
.profile_transfer_request_repository()
.save(
executor,
ProfileTransferRequest::cancel(request_id, current_version),
)
.await?;
let mut updated_request = Some(request);
ProfileTransferRequest::apply(&mut updated_request, event)?;
let updated_request = updated_request.ok_or_else(|| {
Report::new(KernelError::Internal)
.attach_printable("Failed to construct cancelled transfer request")
})?;
deps.profile_transfer_request_read_model()
.update(executor, &updated_request)
.await?;
Ok(())
})
})
.await
}
}
}
101 changes: 101 additions & 0 deletions application/src/service/profile_transfer/cancel/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use super::*;
use crate::service::profile_transfer::test_support::{fixture, Fixture};
use kernel::prelude::entity::{
AccountId, AccountKind, EventVersion, Nanoid, ProfileTransferRequest, ProfileTransferStatus,
};
use kernel::test_utils::AccountBuilder;
use kernel::KernelError;

#[tokio::test]
async fn cancel_succeeds_when_actor_owns_from_account() {
let mut f = fixture();
f.module
.cancel_profile_transfer_request(&f.auth, f.request_nanoid.clone())
.await
.unwrap();

let saved = f
.module
.transfer_request_repository
.saved_events
.lock()
.unwrap();
assert_eq!(saved.len(), 1);
assert!(matches!(
saved[0],
kernel::prelude::entity::ProfileTransferRequestEvent::Cancelled
));
}

#[tokio::test]
async fn cancel_returns_permission_denied_when_actor_does_not_own_from_account() {
let mut f = fixture();
let other_account = AccountBuilder::new()
.id(AccountId::new(500))
.nanoid(Nanoid::new("other"))
.kind(AccountKind::Personal)
.build();
f.module.accounts.owned_accounts = vec![other_account];

let err = f
.module
.cancel_profile_transfer_request(&f.auth, f.request_nanoid.clone())
.await
.unwrap_err();
assert_eq!(err.current_context(), &KernelError::PermissionDenied);
}

#[tokio::test]
async fn cancel_returns_rejected_when_request_already_decided() {
let mut f = fixture();
let decided = {
let request = f
.module
.transfer_request_repository
.request
.as_ref()
.unwrap();
ProfileTransferRequest::new(
request.id().clone(),
f.profile.id().clone(),
f.owner_account.id().clone(),
f.org_account.id().clone(),
ProfileTransferStatus::Accepted,
EventVersion::default(),
request.nanoid().clone(),
)
};
f.module.transfer_request_repository.request = Some(decided);

let err = f
.module
.cancel_profile_transfer_request(&f.auth, f.request_nanoid.clone())
.await
.unwrap_err();
assert_eq!(err.current_context(), &KernelError::Rejected);
}

#[tokio::test]
async fn cancel_returns_not_found_for_unknown_request() {
let f = fixture();

let err = f
.module
.cancel_profile_transfer_request(&f.auth, "unknown".to_string())
.await
.unwrap_err();
assert_eq!(err.current_context(), &KernelError::NotFound);
}

#[tokio::test]
async fn cancel_updates_read_model() {
let mut f = fixture();
f.module
.cancel_profile_transfer_request(&f.auth, f.request_nanoid.clone())
.await
.unwrap();

let updated = f.module.transfer_requests.updated.lock().unwrap();
assert_eq!(updated.len(), 1);
assert_eq!(updated[0].status(), &ProfileTransferStatus::Cancelled);
}
Loading
Loading