Files
AdPlatform-Server/Management/Controllers/Admin/AdminTemplateConfigController.cs
2026-03-14 13:50:09 -07:00

221 lines
9.6 KiB
C#

using Management.Data;
using Management.Security;
using Microsoft.AspNetCore.Mvc;
namespace Management.Controllers.Admin;
/// <summary>
/// Admin endpoints for template configuration metadata —
/// business categories and objective display properties.
///
/// Both are managed through a single stored procedure
/// (spAdminTemplateConfig) with action-based routing.
///
/// CHANNEL ENDPOINTS:
/// GET /api/admin/template-config/channels - List channels (with mapping/template counts)
/// GET /api/admin/template-config/channels/{id} - Get channel
/// POST /api/admin/template-config/channels - Create channel
/// PUT /api/admin/template-config/channels/{id} - Update channel (code rename cascades to mappings + templates)
/// DELETE /api/admin/template-config/channels/{id} - Delete channel (blocked if references exist)
///
/// CATEGORY ENDPOINTS:
/// GET /api/admin/template-config/categories - List categories (with template counts)
/// GET /api/admin/template-config/categories/{id} - Get category
/// POST /api/admin/template-config/categories - Create category
/// PUT /api/admin/template-config/categories/{id} - Update category (rename cascades to templates)
/// DELETE /api/admin/template-config/categories/{id} - Delete category (blocked if templates exist)
///
/// OBJECTIVE ENDPOINTS:
/// GET /api/admin/template-config/objectives - List objectives (with template counts + mapping status)
/// GET /api/admin/template-config/objectives/{id} - Get objective
/// PUT /api/admin/template-config/objectives/{id} - Update objective display properties (color, sort)
///
/// NOTE: Objectives are a controlled set — they cannot be created or deleted
/// because they must stay in sync with tbObjectiveMapping and spInitiative
/// validation. Only display properties (color, sortOrder, isActive) are editable.
/// </summary>
[ApiController]
[Route("api/admin/template-config")]
public sealed class AdminTemplateConfigController : AdminControllerBase
{
public AdminTemplateConfigController(SqlService sql, ClientContext client, ILogger<AdminTemplateConfigController> log)
: base(sql, client, log) { }
// ═══════════════════════════════════════════════════════════
// CATEGORIES
// ═══════════════════════════════════════════════════════════
[HttpGet("categories")]
public Task<IActionResult> ListCategories(CancellationToken ct = default)
=> CallProc("spAdminTemplateConfig", "categories.list", new { }, ct);
[HttpGet("categories/{categoryId:int}")]
public Task<IActionResult> GetCategory(int categoryId, CancellationToken ct)
=> CallProc("spAdminTemplateConfig", "categories.get", new { categoryId }, ct);
[HttpPost("categories")]
public Task<IActionResult> CreateCategory([FromBody] CreateCategoryRequest request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request?.Name))
return Task.FromResult(ValidationError("name is required"));
Logger.LogWarning("[Admin] CreateCategory | Name={Name} | By={User}",
request.Name, Client.Email);
return CallProc("spAdminTemplateConfig", "categories.create", new
{
name = request.Name.Trim(),
icon = request.Icon?.Trim(),
sortOrder = request.SortOrder
}, ct);
}
[HttpPut("categories/{categoryId:int}")]
public Task<IActionResult> UpdateCategory(int categoryId, [FromBody] UpdateCategoryRequest request, CancellationToken ct)
{
Logger.LogWarning("[Admin] UpdateCategory | Id={Id} | By={User}", categoryId, Client.Email);
return CallProc("spAdminTemplateConfig", "categories.update", new
{
categoryId,
name = request?.Name?.Trim(),
icon = request?.Icon?.Trim(),
sortOrder = request?.SortOrder,
isActive = request?.IsActive
}, ct);
}
[HttpDelete("categories/{categoryId:int}")]
public Task<IActionResult> DeleteCategory(int categoryId, CancellationToken ct)
{
Logger.LogWarning("[Admin] DeleteCategory | Id={Id} | By={User}", categoryId, Client.Email);
return CallProc("spAdminTemplateConfig", "categories.delete", new { categoryId }, ct);
}
// ═══════════════════════════════════════════════════════════
// OBJECTIVES
// ═══════════════════════════════════════════════════════════
[HttpGet("objectives")]
public Task<IActionResult> ListObjectives(CancellationToken ct = default)
=> CallProc("spAdminTemplateConfig", "objectives.list", new { }, ct);
[HttpGet("objectives/{objectiveId:int}")]
public Task<IActionResult> GetObjective(int objectiveId, CancellationToken ct)
=> CallProc("spAdminTemplateConfig", "objectives.get", new { objectiveId }, ct);
[HttpPut("objectives/{objectiveId:int}")]
public Task<IActionResult> UpdateObjective(int objectiveId, [FromBody] UpdateObjectiveRequest request, CancellationToken ct)
{
Logger.LogWarning("[Admin] UpdateObjective | Id={Id} | By={User}", objectiveId, Client.Email);
return CallProc("spAdminTemplateConfig", "objectives.update", new
{
objectiveId,
color = request?.Color?.Trim(),
sortOrder = request?.SortOrder,
isActive = request?.IsActive
}, ct);
}
// ═══════════════════════════════════════════════════════════
// CHANNELS
// ═══════════════════════════════════════════════════════════
[HttpGet("channels")]
public Task<IActionResult> ListChannels(CancellationToken ct = default)
=> CallProc("spAdminTemplateConfig", "channels.list", new { }, ct);
[HttpGet("channels/{channelId:int}")]
public Task<IActionResult> GetChannel(int channelId, CancellationToken ct)
=> CallProc("spAdminTemplateConfig", "channels.get", new { channelId }, ct);
[HttpPost("channels")]
public Task<IActionResult> CreateChannel([FromBody] CreateChannelRequest request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request?.Code))
return Task.FromResult(ValidationError("code is required"));
if (string.IsNullOrWhiteSpace(request?.Label))
return Task.FromResult(ValidationError("label is required"));
Logger.LogWarning("[Admin] CreateChannel | Code={Code} | By={User}",
request.Code, Client.Email);
return CallProc("spAdminTemplateConfig", "channels.create", new
{
code = request.Code.Trim().ToLowerInvariant(),
label = request.Label.Trim(),
color = request.Color?.Trim(),
icon = request.Icon?.Trim(),
sortOrder = request.SortOrder
}, ct);
}
[HttpPut("channels/{channelId:int}")]
public Task<IActionResult> UpdateChannel(int channelId, [FromBody] UpdateChannelRequest request, CancellationToken ct)
{
Logger.LogWarning("[Admin] UpdateChannel | Id={Id} | By={User}", channelId, Client.Email);
return CallProc("spAdminTemplateConfig", "channels.update", new
{
channelId,
code = request?.Code?.Trim()?.ToLowerInvariant(),
label = request?.Label?.Trim(),
color = request?.Color?.Trim(),
icon = request?.Icon?.Trim(),
sortOrder = request?.SortOrder,
isActive = request?.IsActive
}, ct);
}
[HttpDelete("channels/{channelId:int}")]
public Task<IActionResult> DeleteChannel(int channelId, CancellationToken ct)
{
Logger.LogWarning("[Admin] DeleteChannel | Id={Id} | By={User}", channelId, Client.Email);
return CallProc("spAdminTemplateConfig", "channels.delete", new { channelId }, ct);
}
}
// ─── DTOs ───────────────────────────────────────────────────
public sealed class CreateCategoryRequest
{
public string? Name { get; set; }
public string? Icon { get; set; }
public int? SortOrder { get; set; }
}
public sealed class UpdateCategoryRequest
{
public string? Name { get; set; }
public string? Icon { get; set; }
public int? SortOrder { get; set; }
public bool? IsActive { get; set; }
}
public sealed class UpdateObjectiveRequest
{
public string? Color { get; set; }
public int? SortOrder { get; set; }
public bool? IsActive { get; set; }
}
public sealed class CreateChannelRequest
{
public string? Code { get; set; }
public string? Label { get; set; }
public string? Color { get; set; }
public string? Icon { get; set; }
public int? SortOrder { get; set; }
}
public sealed class UpdateChannelRequest
{
public string? Code { get; set; }
public string? Label { get; set; }
public string? Color { get; set; }
public string? Icon { get; set; }
public int? SortOrder { get; set; }
public bool? IsActive { get; set; }
}