< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Controllers.ApplicationController
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Controllers/ApplicationController.cs
Line coverage
100%
Covered lines: 36
Uncovered lines: 0
Coverable lines: 36
Total lines: 144
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 7
Fully covered methods: 7
Total methods: 7
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Create()100%11100%
GetById()100%11100%
List()100%11100%
Update()100%11100%
Delete()100%11100%
HardDelete()100%11100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Controllers/ApplicationController.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Input;
 2using ArturRios.Heimdall.Command.Output;
 3using ArturRios.Heimdall.Domain.Enums;
 4using ArturRios.Heimdall.Query.Input;
 5using ArturRios.Heimdall.Query.Output;
 6using ArturRios.Heimdall.Shared.Messages;
 7using ArturRios.Heimdall.WebApi.Security;
 8using ArturRios.Mediator.Command;
 9using ArturRios.Mediator.Query;
 10using ArturRios.Output;
 11using ArturRios.Util.WebApi.AspNetCore;
 12using ArturRios.Util.WebApi.Security.Attributes;
 13using Microsoft.AspNetCore.Mvc;
 14
 15namespace ArturRios.Heimdall.WebApi.Controllers;
 16
 17[Route("api/scopes/{scopeId:guid}/applications")]
 6418public class ApplicationController(CommandMediator commandMediator, QueryMediator queryMediator) : Controller
 19{
 20    /// <summary>
 21    ///     Registers an application within a scope (UC-16, FR-AP-01/02/03). Only a System Admin or a
 22    ///     Scope Admin may call it: FR-AP-03 restricts ownership to an administrator who owns the
 23    ///     scope, so a <c>User</c> has nothing to create here and the attribute refuses them. The
 24    ///     remaining rules depend on data the attribute cannot see and are enforced by the handler —
 25    ///     the acting Scope Admin must own the scope (AF-16e) and may only name themself (AF-16c),
 26    ///     and the named owner must satisfy FR-AP-03 (AF-16b).
 27    /// </summary>
 28    [HttpPost]
 29    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 30    public async Task<ActionResult<DataOutput<CreateApplicationCommandOutput?>>> Create(
 31        Guid scopeId, [FromBody] CreateApplicationCommand command)
 32    {
 1633        command.ScopeId = scopeId;
 1634        HttpContext.ApplyActor(command);
 35
 1636        var result = await commandMediator
 1637            .ExecuteCommandAsync<CreateApplicationCommand, CreateApplicationCommandOutput>(command);
 38
 1639        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 1640    }
 41
 42    /// <summary>
 43    ///     Retrieves a single application by its public identifier within the scope (UC-17,
 44    ///     FR-AP-04/09). A <c>User</c> can own no application (FR-AP-03) and so is refused by the
 45    ///     attribute; among the remaining actors the rule is data-dependent and lives in the handler —
 46    ///     a System Admin sees any application, a Scope Admin only the ones they own (AF-17b).
 47    /// </summary>
 48    [HttpGet("{id:guid}")]
 49    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 50    public async Task<ActionResult<DataOutput<ApplicationOutput?>>> GetById(
 51        Guid scopeId, Guid id, [FromQuery] bool includeDeleted = false)
 52    {
 853        var query = new GetApplicationByIdQuery { ScopeId = scopeId, Id = id, IncludeDeleted = includeDeleted };
 854        HttpContext.ApplyActor(query);
 55
 856        var result = await queryMediator.ExecuteQueryAsync<GetApplicationByIdQuery, ApplicationOutput>(query);
 57
 858        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 859    }
 60
 61    /// <summary>
 62    ///     Lists the applications of a scope (UC-17, FR-AP-05/09). A System Admin sees every
 63    ///     application in the scope; a Scope Admin must own the scope (AF-17b) and sees only the
 64    ///     applications they own. Both narrowings are enforced by the handler from the acting user.
 65    /// </summary>
 66    [HttpGet]
 67    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 68    public async Task<ActionResult<PaginatedOutput<ApplicationOutput>>> List(
 69        Guid scopeId, [FromQuery] ListScopeApplicationsQuery query)
 70    {
 1171        query.ScopeId = scopeId;
 1172        HttpContext.ApplyActor(query);
 73
 1174        var result = await queryMediator
 1175            .ExecutePaginatedQueryAsync<ListScopeApplicationsQuery, ApplicationOutput>(query);
 76
 1177        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 1178    }
 79
 80    /// <summary>
 81    ///     Updates an application's name and owner (UC-18, FR-AP-06). A <c>User</c> can own no
 82    ///     application (FR-AP-03) and so is refused by the attribute; among the remaining actors the
 83    ///     rule is data-dependent and lives in the handler — a System Admin updates any application, a
 84    ///     Scope Admin only the ones they own (AF-18c). The handler also resolves the application
 85    ///     inside the addressed scope (AF-18a) and, when the owner changes, checks the new owner
 86    ///     against FR-AP-03 (AF-18b).
 87    /// </summary>
 88    [HttpPut("{id:guid}")]
 89    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 90    public async Task<ActionResult<DataOutput<UpdateApplicationCommandOutput?>>> Update(
 91        Guid scopeId, Guid id, [FromBody] UpdateApplicationCommand command)
 92    {
 1293        command.ScopeId = scopeId;
 1294        command.Id = id;
 1295        HttpContext.ApplyActor(command);
 96
 1297        var result = await commandMediator
 1298            .ExecuteCommandAsync<UpdateApplicationCommand, UpdateApplicationCommandOutput>(command);
 99
 12100        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 12101    }
 102
 103    /// <summary>
 104    ///     Logically deletes an application by setting <c>IsDeleted = true</c> (UC-19, FR-AP-07). A
 105    ///     <c>User</c> can own no application (FR-AP-03) and so is refused by the attribute; among the
 106    ///     remaining actors the rule is data-dependent and lives in the handler — a System Admin
 107    ///     deletes any application, a Scope Admin only the ones they own (AF-19c). The handler also
 108    ///     resolves the application inside the addressed scope (AF-19a) and answers an already-deleted
 109    ///     application idempotently (AF-19b).
 110    /// </summary>
 111    [HttpDelete("{id:guid}")]
 112    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 113    public async Task<ActionResult<DataOutput<DeleteApplicationCommandOutput?>>> Delete(Guid scopeId, Guid id)
 114    {
 10115        var command = new DeleteApplicationCommand { ScopeId = scopeId, Id = id };
 10116        HttpContext.ApplyActor(command);
 117
 10118        var result = await commandMediator
 10119            .ExecuteCommandAsync<DeleteApplicationCommand, DeleteApplicationCommandOutput>(command);
 120
 10121        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 10122    }
 123
 124    /// <summary>
 125    ///     Permanently (hard) deletes an application, removing the record from the database (UC-20,
 126    ///     FR-AP-08). Restricted to System Admins: a Scope Admin may logically delete an application
 127    ///     they own (UC-19), but never purge it, so the attribute settles authorization on its own and
 128    ///     the handler applies no further rule. The handler resolves the application inside the
 129    ///     addressed scope in any deletion state and reports AF-20a when it does not exist — which
 130    ///     includes a repeated call, as the removal leaves nothing to find. The application's
 131    ///     credentials (UC-53) go with it, by cascade; its scope and owner are untouched.
 132    /// </summary>
 133    [HttpDelete("{id:guid}/hard")]
 134    [RoleRequirement((int)Roles.SystemAdmin)]
 135    public async Task<ActionResult<DataOutput<HardDeleteApplicationCommandOutput?>>> HardDelete(
 136        Guid scopeId, Guid id)
 137    {
 7138        var result = await commandMediator
 7139            .ExecuteCommandAsync<HardDeleteApplicationCommand, HardDeleteApplicationCommandOutput>(
 7140                new HardDeleteApplicationCommand { ScopeId = scopeId, Id = id });
 141
 7142        return result.ToActionResult(statusMap: ApplicationMessageMap.StatusCodes);
 7143    }
 144}