| | | 1 | | using ArturRios.Data.Relational.Core.Interfaces; |
| | | 2 | | using ArturRios.Heimdall.Command.Input; |
| | | 3 | | using ArturRios.Heimdall.Command.Output; |
| | | 4 | | using ArturRios.Heimdall.Domain.Entities; |
| | | 5 | | using ArturRios.Heimdall.Shared.Messages; |
| | | 6 | | using ArturRios.Mediator.Command.Interfaces; |
| | | 7 | | using ArturRios.Output; |
| | | 8 | | using Microsoft.EntityFrameworkCore; |
| | | 9 | | |
| | | 10 | | namespace ArturRios.Heimdall.Command.Handlers; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Handles <see cref="HardDeleteScopePermissionCommand" /> (UC-35, FR-SP-08): locates the |
| | | 14 | | /// permission inside the addressed scope in any deletion state (AF-35a) and permanently removes |
| | | 15 | | /// the record. Nothing cascades — a scope permission is a leaf in the data model, and the scope |
| | | 16 | | /// its foreign key points at is left intact. Authorization is entirely the endpoint's: UC-35's |
| | | 17 | | /// only actor is the System Admin, so no rule is left for the handler to apply. All failures are |
| | | 18 | | /// returned as errors on the <see cref="DataOutput{T}" /> rather than thrown. |
| | | 19 | | /// </summary> |
| | 15 | 20 | | public class HardDeleteScopePermissionCommandHandler( |
| | 15 | 21 | | IAsyncReadOnlyRepository<ScopePermission, long> permissionReader, |
| | 15 | 22 | | IAsyncRepository<ScopePermission, long> permissionWriter) |
| | | 23 | | : ICommandHandlerAsync<HardDeleteScopePermissionCommand, HardDeleteScopePermissionCommandOutput> |
| | | 24 | | { |
| | | 25 | | public async Task<DataOutput<HardDeleteScopePermissionCommandOutput?>> HandleAsync( |
| | | 26 | | HardDeleteScopePermissionCommand command, CancellationToken cancellationToken = default) |
| | | 27 | | { |
| | 16 | 28 | | var output = DataOutput<HardDeleteScopePermissionCommandOutput?>.New; |
| | | 29 | | |
| | | 30 | | // AF-35a: the permission must exist inside the addressed scope. The lookup omits an |
| | | 31 | | // !IsDeleted filter — a permission logically deleted by UC-34 is exactly what a cleanup |
| | | 32 | | // pass starts from and must still be purgeable. The route's scopeId qualifies it: an unknown |
| | | 33 | | // permission, an unknown scope, and a permission living in another scope are all one 404, |
| | | 34 | | // because the addressed resource genuinely does not exist in any of the three cases. A |
| | | 35 | | // repeated call lands here too — the row is already gone, and UC-35 has no idempotent path. |
| | 16 | 36 | | var permission = await permissionReader.Query() |
| | 16 | 37 | | .FirstOrDefaultAsync(x => x.PublicId == command.Id && x.Scope.PublicId == command.ScopeId); |
| | | 38 | | |
| | 16 | 39 | | if (permission is null) |
| | | 40 | | { |
| | 7 | 41 | | return output.WithError(ScopePermissionMessages.ScopePermissionNotFound); |
| | | 42 | | } |
| | | 43 | | |
| | | 44 | | // UC-35 step 2: remove the record for good. No dependent is deleted first — no entity carries |
| | | 45 | | // a foreign key to a scope permission, so no foreign key can be violated and there is no |
| | | 46 | | // total to report. |
| | 9 | 47 | | var delete = await permissionWriter.DeleteAsync(permission); |
| | | 48 | | |
| | 9 | 49 | | if (!delete.Success) |
| | | 50 | | { |
| | 0 | 51 | | return output.WithErrors(delete.Errors); |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | // UC-35 step 3. |
| | 9 | 55 | | return output |
| | 9 | 56 | | .WithData(new HardDeleteScopePermissionCommandOutput { Id = permission.PublicId }) |
| | 9 | 57 | | .WithMessage(ScopePermissionMessages.ScopePermissionHardDeletedSuccessfully); |
| | 16 | 58 | | } |
| | | 59 | | } |