Initial import into Gitea

This commit is contained in:
Grae Jones
2026-03-14 13:50:09 -07:00
parent 8e7e03702e
commit 34c1f09e01
154 changed files with 17666 additions and 1548 deletions

View File

@@ -0,0 +1,84 @@
using Management.Data;
using Management.Security;
using Microsoft.AspNetCore.Mvc;
namespace Management.Controllers.Admin;
/// <summary>
/// Admin endpoints for audience-based allocation modifiers.
/// Table: dbo.tbAllocationModifier
/// Proc: dbo.spAllocationRecommend (list / update actions)
///
/// ENDPOINTS:
/// GET /api/admin/modifiers - List all modifiers
/// PUT /api/admin/modifiers/{id} - Update a modifier
/// POST /api/admin/modifiers/preview - Preview recommendation with factors
/// </summary>
[ApiController]
[Route("api/admin/modifiers")]
public sealed class AdminModifiersController : AdminControllerBase
{
public AdminModifiersController(SqlService sql, ClientContext client, ILogger<AdminModifiersController> log)
: base(sql, client, log) { }
[HttpGet]
public Task<IActionResult> List(CancellationToken ct)
=> CallProc("spAllocationRecommend", "list", new { }, ct);
[HttpPut("{id:int}")]
public Task<IActionResult> Update(int id, [FromBody] UpdateModifierRequest request, CancellationToken ct)
{
if (request?.PctAdjustment is < -50 or > 50)
return Task.FromResult(ValidationError("pctAdjustment must be between -50 and 50"));
Logger.LogWarning("[Admin] UpdateModifier | Id={Id} | By={User}", id, Client.Email);
return CallProc("spAllocationRecommend", "update", new
{
id,
pctAdjustment = request?.PctAdjustment,
minBudgetAdj = request?.MinBudgetAdj,
rationale = request?.Rationale?.Trim(),
isActive = request?.IsActive
}, ct);
}
/// <summary>
/// Preview a recommendation with given factors — same proc, recommend action.
/// Lets admins test how modifiers affect channel mix without going through the wizard.
/// </summary>
[HttpPost("preview")]
public Task<IActionResult> Preview([FromBody] PreviewRequest request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request?.BusinessCategory))
return Task.FromResult(ValidationError("businessCategory is required"));
if (string.IsNullOrWhiteSpace(request?.Objective))
return Task.FromResult(ValidationError("objective is required"));
return CallProc("spAllocationRecommend", "recommend", new
{
businessCategory = request.BusinessCategory.Trim(),
objective = request.Objective.Trim(),
ageSkew = request.AgeSkew?.Trim(),
marketScope = request.MarketScope?.Trim()
}, ct);
}
}
// DTOs
public sealed class UpdateModifierRequest
{
public int? PctAdjustment { get; set; }
public int? MinBudgetAdj { get; set; }
public string? Rationale { get; set; }
public bool? IsActive { get; set; }
}
public sealed class PreviewRequest
{
public string? BusinessCategory { get; set; }
public string? Objective { get; set; }
public string? AgeSkew { get; set; }
public string? MarketScope { get; set; }
}