< Summary - Heimdall API

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

Metrics

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

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Controllers/ScopeController.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")]
 6218public class ScopeController(CommandMediator commandMediator, QueryMediator queryMediator) : Controller
 19{
 20    /// <summary>
 21    ///     Creates a new scope with one or more initial owners (UC-01). Restricted to System Admins.
 22    /// </summary>
 23    [HttpPost]
 24    [RoleRequirement((int)Roles.SystemAdmin)]
 25    public async Task<ActionResult<DataOutput<CreateScopeCommandOutput?>>> Create([FromBody] CreateScopeCommand command)
 26    {
 827        var result = await commandMediator
 828            .ExecuteCommandAsync<CreateScopeCommand, CreateScopeCommandOutput>(command);
 29
 830        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 831    }
 32
 33    /// <summary>
 34    ///     Updates an existing scope's name and description (UC-03). Restricted to System Admins.
 35    /// </summary>
 36    [HttpPut("{id:guid}")]
 37    [RoleRequirement((int)Roles.SystemAdmin)]
 38    public async Task<ActionResult<DataOutput<UpdateScopeCommandOutput?>>> Update(
 39        Guid id, [FromBody] UpdateScopeCommand command)
 40    {
 541        command.Id = id;
 42
 543        var result = await commandMediator
 544            .ExecuteCommandAsync<UpdateScopeCommand, UpdateScopeCommandOutput>(command);
 45
 546        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 547    }
 48
 49    /// <summary>
 50    ///     Logically deletes a scope, cascading to its Users, Google Users, and applications (UC-04).
 51    ///     Restricted to System Admins.
 52    /// </summary>
 53    [HttpDelete("{id:guid}")]
 54    [RoleRequirement((int)Roles.SystemAdmin)]
 55    public async Task<ActionResult<DataOutput<DeleteScopeCommandOutput?>>> Delete(Guid id)
 56    {
 457        var result = await commandMediator
 458            .ExecuteCommandAsync<DeleteScopeCommand, DeleteScopeCommandOutput>(new DeleteScopeCommand { Id = id });
 59
 460        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 461    }
 62
 63    /// <summary>
 64    ///     Permanently (hard) deletes a scope, removing its Users, Google Users, applications,
 65    ///     scope permissions, and ownership/membership join rows (UC-05). Restricted to System
 66    ///     Admins.
 67    /// </summary>
 68    [HttpDelete("{id:guid}/hard")]
 69    [RoleRequirement((int)Roles.SystemAdmin)]
 70    public async Task<ActionResult<DataOutput<HardDeleteScopeCommandOutput?>>> HardDelete(Guid id)
 71    {
 372        var result = await commandMediator
 373            .ExecuteCommandAsync<HardDeleteScopeCommand, HardDeleteScopeCommandOutput>(
 374                new HardDeleteScopeCommand { Id = id });
 75
 376        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 377    }
 78
 79    /// <summary>
 80    ///     Turns Google Sign-In on or off for a scope (UC-24, FR-GO-01/FR-GO-02). The attribute keeps
 81    ///     a <c>User</c> out — they can never be a System Admin nor an existing owner — while the
 82    ///     rules that depend on data it cannot see are the handler's: whether the acting Scope Admin
 83    ///     owns this scope (AF-24b) and whether the scope is active (AF-24a).
 84    /// </summary>
 85    [HttpPut("{id:guid}/google-signin")]
 86    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 87    public async Task<ActionResult<DataOutput<SetGoogleSignInCommandOutput?>>> SetGoogleSignIn(
 88        Guid id, [FromBody] SetGoogleSignInCommand command)
 89    {
 990        command.Id = id;
 991        HttpContext.ApplyActor(command);
 92
 993        var result = await commandMediator
 994            .ExecuteCommandAsync<SetGoogleSignInCommand, SetGoogleSignInCommandOutput>(command);
 95
 996        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 997    }
 98
 99    /// <summary>
 100    ///     Opens or closes anonymous self-registration for a scope (UC-47 toggle, FR-SR-02). Same
 101    ///     actors and refusals as the Google Sign-In toggle (UC-24).
 102    /// </summary>
 103    [HttpPut("{id:guid}/self-registration")]
 104    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 105    public async Task<ActionResult<DataOutput<SetSelfRegistrationCommandOutput?>>> SetSelfRegistration(
 106        Guid id, [FromBody] SetSelfRegistrationCommand command)
 107    {
 1108        command.Id = id;
 1109        HttpContext.ApplyActor(command);
 110
 1111        var result = await commandMediator
 1112            .ExecuteCommandAsync<SetSelfRegistrationCommand, SetSelfRegistrationCommandOutput>(command);
 113
 1114        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 1115    }
 116
 117    /// <summary>
 118    ///     Sets the base links the scope's verification and password-reset emails point at. A client
 119    ///     system with its own screens for those steps points its users there; a link left out, or
 120    ///     sent as null or empty, is cleared and the global one applies again. Same actors and
 121    ///     refusals as the self-registration toggle.
 122    /// </summary>
 123    [HttpPut("{id:guid}/email-links")]
 124    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 125    public async Task<ActionResult<DataOutput<SetScopeEmailLinksCommandOutput?>>> SetEmailLinks(
 126        Guid id, [FromBody] SetScopeEmailLinksCommand command)
 127    {
 5128        command.Id = id;
 5129        HttpContext.ApplyActor(command);
 130
 5131        var result = await commandMediator
 5132            .ExecuteCommandAsync<SetScopeEmailLinksCommand, SetScopeEmailLinksCommandOutput>(command);
 133
 5134        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 5135    }
 136
 137    /// <summary>
 138    ///     Lists the scope's self-registrations awaiting confirmation (UC-49, FR-SR-11), newest first:
 139    ///     each one's id, name, address, when it was stored and expires, how many wrong passwords it
 140    ///     may still take, and whether it has already expired. Never the password hash, salt or token.
 141    ///     The attribute keeps a <c>User</c> out; whether the acting Scope Admin owns the scope
 142    ///     (AF-49b) and whether the scope is live (AF-49a) are the handler's.
 143    /// </summary>
 144    [HttpGet("{id:guid}/pending-registrations")]
 145    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 146    public async Task<ActionResult<PaginatedOutput<PendingRegistrationOutput>>> ListPendingRegistrations(
 147        Guid id, [FromQuery] ListPendingRegistrationsQuery query)
 148    {
 8149        query.ScopeId = id;
 8150        HttpContext.ApplyActor(query);
 151
 8152        var result = await queryMediator
 8153            .ExecutePaginatedQueryAsync<ListPendingRegistrationsQuery, PendingRegistrationOutput>(query);
 154
 8155        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 8156    }
 157
 158    /// <summary>
 159    ///     Discards one of the scope's self-registrations awaiting confirmation (UC-50, FR-SR-12). Its
 160    ///     link stops working — following it answers as for any unknown token — and the registrant may
 161    ///     sign up again. Same actors as the listing; a registration of another scope answers 404 exactly
 162    ///     like a missing one (AF-50c).
 163    /// </summary>
 164    [HttpDelete("{id:guid}/pending-registrations/{registrationId:guid}")]
 165    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 166    public async Task<ActionResult<DataOutput<DiscardPendingRegistrationCommandOutput?>>> DiscardPendingRegistration(
 167        Guid id, Guid registrationId)
 168    {
 6169        var command = new DiscardPendingRegistrationCommand { ScopeId = id, RegistrationId = registrationId };
 6170        HttpContext.ApplyActor(command);
 171
 6172        var result = await commandMediator
 6173            .ExecuteCommandAsync<DiscardPendingRegistrationCommand, DiscardPendingRegistrationCommandOutput>(command);
 174
 6175        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 6176    }
 177
 178    /// <summary>
 179    ///     Lists scopes with pagination and optional filtering (UC-02 read b). A System Admin lists
 180    ///     every scope; a Scope Admin only the scopes they own, with the total counting those alone.
 181    /// </summary>
 182    /// <remarks>
 183    ///     Ownership is read from the database rather than the token's owned-scope claim, so an owner
 184    ///     removed from a scope stops seeing it at once. A <c>User</c> has one scope and reads it by
 185    ///     id, so the collection is refused to them (403).
 186    /// </remarks>
 187    [HttpGet]
 188    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 189    public async Task<ActionResult<PaginatedOutput<ScopeOutput>>> List([FromQuery] ListScopesQuery query)
 190    {
 4191        HttpContext.ApplyActor(query);
 192
 4193        var result = await queryMediator
 4194            .ExecutePaginatedQueryAsync<ListScopesQuery, ScopeOutput>(query);
 195
 4196        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 4197    }
 198
 199    /// <summary>
 200    ///     Retrieves a single scope by its public identifier (UC-02). Open to any authenticated actor
 201    ///     because a Scope Admin reads the scopes they own and a User the scope they belong to; that
 202    ///     per-actor visibility rule (AF-02b) is data-dependent and is therefore enforced by the
 203    ///     handler.
 204    /// </summary>
 205    [HttpGet("{id:guid}")]
 206    public async Task<ActionResult<DataOutput<ScopeOutput?>>> GetById(Guid id, [FromQuery] bool includeDeleted = false)
 207    {
 7208        var query = new GetScopeByIdQuery { Id = id, IncludeDeleted = includeDeleted };
 7209        HttpContext.ApplyActor(query);
 210
 7211        var result = await queryMediator.ExecuteQueryAsync<GetScopeByIdQuery, ScopeOutput>(query);
 212
 7213        return result.ToActionResult(statusMap: ScopeMessageMap.StatusCodes);
 7214    }
 215}