< Summary

Information
Class: Chronicis.Api.Controllers.CampaignsController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/CampaignsController.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 189
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/CampaignsController.cs

#LineLine coverage
 1using Chronicis.Api.Infrastructure;
 2using Chronicis.Api.Models;
 3using Chronicis.Api.Services;
 4using Chronicis.Shared.DTOs;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace Chronicis.Api.Controllers;
 9
 10/// <summary>
 11/// API endpoints for Campaign management.
 12/// </summary>
 13[ApiController]
 14[Route("campaigns")]
 15[Authorize]
 16public class CampaignsController : ControllerBase
 17{
 18    private readonly ICampaignService _campaignService;
 19    private readonly ICurrentUserService _currentUserService;
 20    private readonly ILogger<CampaignsController> _logger;
 21
 122    public CampaignsController(
 123        ICampaignService campaignService,
 124        ICurrentUserService currentUserService,
 125        ILogger<CampaignsController> logger)
 26    {
 127        _campaignService = campaignService;
 128        _currentUserService = currentUserService;
 129        _logger = logger;
 130    }
 31
 32    /// <summary>
 33    /// GET /api/campaigns/{id} - Get a specific campaign.
 34    /// </summary>
 35    [HttpGet("{id:guid}")]
 36    public async Task<ActionResult<CampaignDto>> GetCampaign(Guid id)
 37    {
 38        var user = await _currentUserService.GetRequiredUserAsync();
 39        _logger.LogTraceSanitized("Getting campaign {CampaignId} for user {UserId}", id, user.Id);
 40
 41        var campaign = await _campaignService.GetCampaignAsync(id, user.Id);
 42
 43        if (campaign == null)
 44        {
 45            return NotFound(new { error = "Campaign not found or access denied" });
 46        }
 47
 48        return Ok(campaign);
 49    }
 50
 51    /// <summary>
 52    /// POST /api/campaigns - Create a new campaign.
 53    /// </summary>
 54    [HttpPost]
 55    public async Task<ActionResult<CampaignDto>> CreateCampaign([FromBody] CampaignCreateDto dto)
 56    {
 57        var user = await _currentUserService.GetRequiredUserAsync();
 58
 59        if (dto == null || string.IsNullOrWhiteSpace(dto.Name))
 60        {
 61            return BadRequest(new { error = "Name is required" });
 62        }
 63
 64        if (dto.WorldId == Guid.Empty)
 65        {
 66            return BadRequest(new { error = "WorldId is required" });
 67        }
 68
 69        _logger.LogTraceSanitized("Creating campaign '{Name}' in world {WorldId} for user {UserId}",
 70            dto.Name, dto.WorldId, user.Id);
 71
 72        try
 73        {
 74            var campaign = await _campaignService.CreateCampaignAsync(dto, user.Id);
 75            return CreatedAtAction(nameof(GetCampaign), new { id = campaign.Id }, campaign);
 76        }
 77        catch (UnauthorizedAccessException ex)
 78        {
 79            return StatusCode(403, new { error = ex.Message });
 80        }
 81        catch (InvalidOperationException ex)
 82        {
 83            return BadRequest(new { error = ex.Message });
 84        }
 85    }
 86
 87    /// <summary>
 88    /// PUT /api/campaigns/{id} - Update a campaign.
 89    /// </summary>
 90    [HttpPut("{id:guid}")]
 91    public async Task<ActionResult<CampaignDto>> UpdateCampaign(Guid id, [FromBody] CampaignUpdateDto dto)
 92    {
 93        var user = await _currentUserService.GetRequiredUserAsync();
 94
 95        if (dto == null || string.IsNullOrWhiteSpace(dto.Name))
 96        {
 97            return BadRequest(new { error = "Name is required" });
 98        }
 99
 100        _logger.LogTraceSanitized("Updating campaign {CampaignId} for user {UserId}", id, user.Id);
 101
 102        var campaign = await _campaignService.UpdateCampaignAsync(id, dto, user.Id);
 103
 104        if (campaign == null)
 105        {
 106            return NotFound(new { error = "Campaign not found or access denied" });
 107        }
 108
 109        return Ok(campaign);
 110    }
 111
 112    /// <summary>
 113    /// POST /api/campaigns/{id}/activate - Activate a campaign.
 114    /// </summary>
 115    [HttpPost("{id:guid}/activate")]
 116    public async Task<IActionResult> ActivateCampaign(Guid id)
 117    {
 118        var user = await _currentUserService.GetRequiredUserAsync();
 119
 120        _logger.LogTraceSanitized("Activating campaign {CampaignId} for user {UserId}", id, user.Id);
 121
 122        var success = await _campaignService.ActivateCampaignAsync(id, user.Id);
 123
 124        if (!success)
 125        {
 126            return BadRequest(new { error = "Unable to activate campaign. Campaign not found or you don't have permissio
 127        }
 128
 129        return NoContent();
 130    }
 131
 132    /// <summary>
 133    /// PUT /api/campaigns/{id}/slug - Update a campaign's slug.
 134    /// </summary>
 135    [HttpPut("{id:guid}/slug")]
 136    public async Task<ActionResult<SlugUpdateResponseDto>> UpdateCampaignSlug(Guid id, [FromBody] SlugUpdateRequestDto d
 137    {
 138        var user = await _currentUserService.GetRequiredUserAsync();
 139
 140        var result = await _campaignService.UpdateSlugAsync(id, dto.Slug, user.Id);
 141
 142        return result.Status switch
 143        {
 144            ServiceStatus.Success => Ok(new SlugUpdateResponseDto { Slug = result.Value! }),
 145            ServiceStatus.NotFound => NotFound(new { error = "Campaign not found" }),
 146            ServiceStatus.Forbidden => StatusCode(403, new { error = result.ErrorMessage }),
 147            ServiceStatus.ValidationError when result.ErrorMessage == "SLUG_RESERVED" =>
 148                BadRequest(new { error = "SLUG_RESERVED" }),
 149            _ => BadRequest(new { error = result.ErrorMessage })
 150        };
 151    }
 152}
 153
 154/// <summary>
 155/// Active context endpoints - nested under worlds but related to campaigns.
 156/// </summary>
 157[ApiController]
 158[Route("worlds/{worldId:guid}")]
 159[Authorize]
 160public class WorldActiveContextController : ControllerBase
 161{
 162    private readonly ICampaignService _campaignService;
 163    private readonly ICurrentUserService _currentUserService;
 164    private readonly ILogger<WorldActiveContextController> _logger;
 165
 166    public WorldActiveContextController(
 167        ICampaignService campaignService,
 168        ICurrentUserService currentUserService,
 169        ILogger<WorldActiveContextController> logger)
 170    {
 171        _campaignService = campaignService;
 172        _currentUserService = currentUserService;
 173        _logger = logger;
 174    }
 175
 176    /// <summary>
 177    /// GET /api/worlds/{worldId}/active-context - Get the active context for a world.
 178    /// </summary>
 179    [HttpGet("active-context")]
 180    public async Task<ActionResult<ActiveContextDto>> GetActiveContext(Guid worldId)
 181    {
 182        var user = await _currentUserService.GetRequiredUserAsync();
 183
 184        _logger.LogTraceSanitized("Getting active context for world {WorldId} for user {UserId}", worldId, user.Id);
 185
 186        var activeContext = await _campaignService.GetActiveContextAsync(worldId, user.Id);
 187        return Ok(activeContext);
 188    }
 189}