Skip to content

Commit

Permalink
Delete of Organizations and persons #178
Browse files Browse the repository at this point in the history
  • Loading branch information
lucabruno91 committed Mar 5, 2024
1 parent 96701d2 commit 857a785
Show file tree
Hide file tree
Showing 24 changed files with 5,398 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Ermes.Organizations.Dto
namespace Ermes.Organizations.Dto
{
public class DeleteOrganizationInput
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public class OrganizationDto
public string ParentName { get; set; }
public bool MembersHaveTaxCode { get; set; }
public bool HasChildren { get; set; }
public bool IsActive { get; set; }
}
}
133 changes: 118 additions & 15 deletions src/Ermes.Application/Ermes/Organizations/OrganizationsAppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@
using Abp.UI;
using Ermes.Attributes;
using Ermes.Authorization;
using Ermes.Communications;
using Ermes.CompetenceAreas;
using Ermes.Dto.Datatable;
using Ermes.Gamification;
using Ermes.Linq.Extensions;
using Ermes.MapRequests;
using Ermes.Missions;
using Ermes.Notifications;
using Ermes.Operations;
using Ermes.Organizations.Dto;
using Ermes.Persons;
using Ermes.Reports;
using Ermes.Teams;
using FusionAuthNetCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -19,23 +30,51 @@ namespace Ermes.Organizations
public class OrganizationsAppService : ErmesAppServiceBase, IOrganizationsAppService
{
private readonly OrganizationManager _organizationManager;
private readonly TeamManager _teamManager;
private readonly CompetenceAreaManager _compAreaManager;
private readonly PersonManager _personManager;
private readonly ErmesAppSession _session;
private readonly ErmesPermissionChecker _permissionChecker;
private readonly NotificationManager _notificationManager;
private readonly OperationManager _operationManager;
private readonly ReportManager _reportManager;
private readonly MapRequestManager _mapRequestManager;
private readonly CommunicationManager _communicationManager;
private readonly IOptions<FusionAuthSettings> _fusionAuthSettings;
private readonly MissionManager _missionManager;
private readonly GamificationManager _gamificationManager;

public OrganizationsAppService(
OrganizationManager organizationManager,
TeamManager teamManager,
CompetenceAreaManager compAreaManager,
PersonManager personManager,
ErmesAppSession session,
ErmesPermissionChecker permissionChecker
ErmesPermissionChecker permissionChecker,
NotificationManager notificationManager,
OperationManager operationManager,
ReportManager reportManager,
MapRequestManager mapRequestManager,
CommunicationManager communicationManager,
MissionManager missionManager,
GamificationManager gamificationManager,
IOptions<FusionAuthSettings> fusionAuthSettings
)
{
_organizationManager = organizationManager;
_teamManager = teamManager;
_compAreaManager = compAreaManager;
_personManager = personManager;
_session = session;
_permissionChecker = permissionChecker;
_fusionAuthSettings = fusionAuthSettings;
_notificationManager = notificationManager;
_operationManager = operationManager;
_reportManager = reportManager;
_communicationManager = communicationManager;
_mapRequestManager = mapRequestManager;
_missionManager = missionManager;
_gamificationManager = gamificationManager;
}

#region Private Methods
Expand All @@ -60,7 +99,7 @@ private async Task<int> CreateOrganization(OrganizationDto newOrganization)
throw new UserFriendlyException(L("InvalidParentId", newOrganization.ParentId.Value));

var newOrg = ObjectMapper.Map<Organization>(newOrganization);

newOrg.IsActive = true;
var newOrgId = await _organizationManager.InsertOrganizationAsync(newOrg);
Logger.Info("Ermes: CreateOrganization with new Id: " + newOrgId);
return newOrgId;
Expand Down Expand Up @@ -119,6 +158,27 @@ private async Task<PagedResultDto<OrganizationDto>> InternalGetOrganizations(Get
result.Items = ObjectMapper.Map<List<OrganizationDto>>(items);
return result;
}

private async Task<bool> BulkDeleteUsersAsync(int organizationId)
{
var client = FusionAuth.GetFusionAuthClient(_fusionAuthSettings.Value);
var persons = await _personManager.GetPersonsByOrganizationIdAsync(organizationId);
var response = await client.DeleteUsersByQueryAsync(new io.fusionauth.domain.api.UserDeleteRequest()
{
hardDelete = true,
userIds = persons.Select(a => a.FusionAuthUserGuid).ToList()
});

if (response.WasSuccessful())
{
return true;
}
else
{
var fa_error = FusionAuth.ManageErrorResponse(response);
throw new UserFriendlyException(fa_error.ErrorCode, fa_error.HasTranslation ? L(fa_error.Message) : fa_error.Message);
}
}
#endregion
public virtual async Task<CreateOrUpdateOrganizationOutput> CreateOrUpdateOrganization(CreateOrUpdateOrganizationInput input)
{
Expand All @@ -134,39 +194,82 @@ public virtual async Task<CreateOrUpdateOrganizationOutput> CreateOrUpdateOrgani
return res;
}


public virtual async Task<DTResult<OrganizationDto>> GetOrganizations(GetOrganizationsInput input)
{
PagedResultDto<OrganizationDto> result = await InternalGetOrganizations(input);
return new DTResult<OrganizationDto>(input.Draw, result.TotalCount, result.Items.Count, result.Items.ToList());
}

[OpenApiOperation("Delete Organization",
@"
Remove:
- organization (soft)
- teams (hard)
- members of the organization (soft)
Input: OrganizationId
Output: true if the operation completes with success
Note: Admin can delete father organizations,
while organization managers can only delete organizations that are child of the org they belong to
"
)]
public virtual async Task<bool> DeleteOrganization(DeleteOrganizationInput input)
{
var org = await _organizationManager.GetOrganizationByIdAsync(input.OrganizationId);
if (org == null)
throw new UserFriendlyException(L("InvalidOrganizationId", input.OrganizationId));

if (!await _personManager.CanOrganizationBeDeletedAsync(input.OrganizationId))
throw new UserFriendlyException(L("OrganizationCannotBeDeleted", input.OrganizationId));
if (!_permissionChecker.IsGranted(_session.Roles, AppPermissions.Organizations.Organization_CanDelete))
throw new UserFriendlyException(L("MissingPermission"));

var person = _session.LoggedUserPerson;
if (!person.OrganizationId.HasValue)

if (person.OrganizationId.HasValue && person.OrganizationId.Value != org.ParentId)
throw new UserFriendlyException(L("MissingPermission"));
else
{
if (_permissionChecker.IsGranted(_session.Roles, AppPermissions.Organizations.Organization_CanDeleteCrossOrganization))
await _organizationManager.DeleteOrganizationAsync(input.OrganizationId);
else
if (!_permissionChecker.IsGranted(_session.Roles, AppPermissions.Organizations.Organization_CanDeleteCrossOrganization))
throw new UserFriendlyException(L("MissingPermission"));
if (!await _personManager.CanOrganizationBeDeletedAsync(input.OrganizationId))
throw new UserFriendlyException(L("OrganizationCannotBeDeleted", input.OrganizationId));
}
else


var persons = await _personManager.GetPersonsByOrganizationIdAsync(input.OrganizationId);

foreach (var item in persons)
{
if (person.OrganizationId.Value == org.Id || person.OrganizationId.Value == org.ParentId)
await _organizationManager.DeleteOrganizationAsync(input.OrganizationId);
else
throw new UserFriendlyException(L("MissingPermission"));
item.TeamId = null;
item.Team = null;
}
var teams = await _teamManager.GetTeamsByOrganizationIdAsync(input.OrganizationId);
foreach (var item in teams)
{
await _teamManager.DeleteTeamAsync(item.Id);
Logger.Info(string.Format("Ermes: Deleted Team {0} with Id {1}", item.Name, item.Id));
}

Logger.Info("Ermes: DeleteOrganization with Id: " + input.OrganizationId + "by Person: " + person.Username);
foreach (var item in persons)
{
await DeleteUserInternalAsync(
item.FusionAuthUserGuid,
item,
false,
_notificationManager,
_operationManager,
_reportManager,
_communicationManager,
_mapRequestManager,
_missionManager,
_personManager,
_gamificationManager,
_fusionAuthSettings
);
Logger.Info(string.Format("Ermes: Deleted Person {0} with Id {1}", item.Username, item.Id));
}

org.IsActive = false;
Logger.Info(string.Format("Ermes: De-activate Organization {0} with Id {1} by {2}", org.Name, input.OrganizationId, person.Username));

return true;
}

Expand Down
10 changes: 10 additions & 0 deletions src/Ermes.Application/Ermes/Profile/Dto/DeleteProfileInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Ermes.Profile.Dto
{
public class DeleteProfileInput
{
public Guid Id { get; set; }
public bool HardDelete { get; set; }
}
}
17 changes: 17 additions & 0 deletions src/Ermes.Application/Ermes/Profile/Dto/ReactivateProfileInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Abp.Runtime.Validation;
using System.ComponentModel.DataAnnotations;

namespace Ermes.Profile.Dto
{
public class ReactivateProfileInput : ICustomValidate
{
[Required]
public string Email { get; set; }

public void AddValidationErrors(CustomValidationContext context)
{
if(Email == null || Email == string.Empty)
context.Results.Add(new ValidationResult("Email address required"));
}
}
}
3 changes: 2 additions & 1 deletion src/Ermes.Application/Ermes/Profile/IProfileAppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public interface IProfileAppService : IApplicationService
Task<GetProfileOutput> GetProfileById(IdInput<long> input);
Task<UpdateProfileOutput> UpdateProfile(UpdateProfileInput input);
//Task<bool> UpdatePreferredLanguages(UpdatePreferredLanguagesInput input);
Task<bool> DeleteProfile(IdInput<Guid> input);
Task<bool> DeleteProfile(DeleteProfileInput input);
Task<GetProfileOutput> ReactivateProfile(ReactivateProfileInput input);
Task<bool> UpdateRegistrationToken(UpdateRegistrationTokenInput input);
Task<DTResult<PersonDto>> GetOrganizationMembers(GetOrganizationMembersInput input);
Task<DTResult<OrganizationDto>> GetOrganizations(GetOrganizationsInput input);
Expand Down
93 changes: 69 additions & 24 deletions src/Ermes.Application/Ermes/Profile/ProfileAppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,18 +307,21 @@ public virtual async Task<UpdateProfileOutput> UpdateProfile(UpdateProfileInput

[OpenApiOperation("Delete profile",
@"
Completely remove the person from the system
Input: Guid provided by FusionAuth
Output: true is the user, and all the associated resources, have been properly deleted
Remove the person from the system
Input:
- Guid provided by FusionAuth
- HardDelete: if true, delete every info associated to the person, if false, deactivate account on FusionAuth
Output: true is the operation ends successfully
Notes: a user can be deleted by org manager of the same organization or belonging to a parent organization
"
)]
public virtual async Task<bool> DeleteProfile(IdInput<Guid> input)
public virtual async Task<bool> DeleteProfile(DeleteProfileInput input)
{
Guid refGuid;

//The operation can be performed by:
// - admin
// - the user itself
// - the user on his own account
if (input == null || input.Id == null || input.Id == Guid.Empty)
refGuid = _session.FusionAuthUserGuid.Value;
else
Expand All @@ -329,36 +332,78 @@ public virtual async Task<bool> DeleteProfile(IdInput<Guid> input)

refGuid = input.Id;
}

var person = await _personManager.GetPersonByFusionAuthUserGuidAsync(refGuid);

await _notificationManager.DeleteNotificationsByPersonIdAsync(person.Id);
await _operationManager.DeleteOperationsByPersonIdAsync(person.Id);
await _reportManager.DeleteReportsByPersonIdAsync(person.Id);
await _communicationManager.DeleteCommunicationsByPersonIdAsync(person.Id);
await _mapRequestManager.DeleteMapRequestsByPersonIdAsync(person.Id);
await _missionManager.DeleteMissionsByPersonIdAsync(person.Id);
await _personManager.DeletePersonActionsByPersonIdAsync(person.Id);
await _personManager.DeletePersonRolesByPersonIdAsync(person.Id);

var roles = await _personManager.GetPersonRoleNamesAsync(person.Id);
if (roles.Any(r => r == AppRoles.CITIZEN))//remove gamification items
if (person.OrganizationId.HasValue)
{
await _gamificationManager.DeleteAuditByPersonIdAsync(person.Id);
await _personManager.DeletePersonQuizzesByPersonIdAsync(person.Id);
await _personManager.DeletePersonTipsByPersonIdAsync(person.Id);
var loggedPerson = _session.LoggedUserPerson;
if (loggedPerson.OrganizationId != person.OrganizationId.Value && loggedPerson.OrganizationId != person.Organization.ParentId)
throw new UserFriendlyException("EntityOutsideOrganization");
}

return await DeleteUserInternalAsync(
refGuid,
person,
input.HardDelete,
_notificationManager,
_operationManager,
_reportManager,
_communicationManager,
_mapRequestManager,
_missionManager,
_personManager,
_gamificationManager,
_fusionAuthSettings
);
}

[OpenApiOperation("Reactivate profile",
@"
Reactivate an already existing profile
Input:
- Email address of the profile to be reactiveted
Output: ProfileDto object
Notes: a user can be reactivated by org manager of the same organization or belonging to a parent organization
"
)]
public virtual async Task<GetProfileOutput> ReactivateProfile(ReactivateProfileInput input)
{
var person = _personManager.GetPersonByEmail(input.Email);
if (person == null)
throw new UserFriendlyException(L("InvalidEmailAddress"));
if (person.FusionAuthUserGuid == Guid.Empty)
throw new UserFriendlyException(L("InvalidGuid"));

var hasPermission = _permissionChecker.IsGranted(_session.Roles, AppPermissions.Profiles.Profile_CanReactivate);
if (!hasPermission)
throw new UserFriendlyException("MissingPermission");

//no additional checks needed for citizens
if (person.OrganizationId.HasValue) {
var loggedPerson = _session.LoggedUserPerson;
if (loggedPerson.OrganizationId != person.OrganizationId.Value && loggedPerson.OrganizationId != person.Organization.ParentId)
throw new UserFriendlyException("EntityOutsideOrganization");
}

if (person.IsActive)
throw new UserFriendlyException(L("PersonIsActive"));

var client = FusionAuth.GetFusionAuthClient(_fusionAuthSettings.Value);
var response = await client.DeleteUserAsync(refGuid);

var response = await client.ReactivateUserAsync(person.FusionAuthUserGuid);
if (!response.WasSuccessful())
{
var fa_error = FusionAuth.ManageErrorResponse(response);
throw new UserFriendlyException(fa_error.ErrorCode, fa_error.HasTranslation ? L(fa_error.Message) : fa_error.Message);
}

await _personManager.DeletePersonByIdAsync(person.Id);
return true;

person.IsActive = true;
return new GetProfileOutput()
{
Profile = await GetProfileInternal(person, response.successResponse.user, _personManager, _missionManager, _gamificationManager, _session, _jobManager)
};

}

[OpenApiOperation("Update Registration Token",
Expand Down
1 change: 0 additions & 1 deletion src/Ermes.Application/Ermes/Users/IUsersAppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public interface IUsersAppService : IBackofficeApi
Task<DTResult<ProfileDto>> GetUsers(GetUsersInput input);
Task<CreateOrUpdateUserOutput> CreateOrUpdateUser(UpdateProfileInput input);
Task<DTResult<ProfileDto>> GetUncompletedUsers(GetUncompletedUsersInput input);

}
}
Loading

0 comments on commit 857a785

Please sign in to comment.